Copyright: Delphi v.3.0 Copyright (c)1996 Borland International, Inc.


Introduction

About this release

Welcome to the Ivory Field Test III. We thank you for maintaining strict confidentiality about the characteristics and existence of this release of Ivory. Please refrain from discussing the Ivory Field Test in any public forum. Unless otherwise noted, all materials provided in this field test software are copyright (C) 1996 by Borland, Inc.

 Please note that distributing applications generated with this field-test version of Ivory is prohibited.

About these release notes

This is preliminary, draft documentation, intended to help you understand new and evolving Ivory features. Some information may not completely reflect the state of the software.

The field test notes include information on:

Using Packages

Using DistributedDatasets

Using COM Interfaces

Creating an Active Form

Creating Internet Applications

Miscellaneous Enhancements


Package Support
Introduction

Packages are specially compiled dynamic-link libraries (DLLs). They allow code-sharing among applications, reducing executable size and conserving system resources. The Component Library has been re-engineered to take advantage of package technology. A specific package can be compiled without rebuilding the entire Library, and packages can be loaded on a per-project basis (so that only the packages used by the current project need to be loaded). Another benefit of packages is a reduction (by about one megabyte) in the size of the IDE, because the IDE itself uses the same copy of the VCL30.DLL package as the Component Library.

The IDE has been enhanced in several ways to accommodate packages. A Package Editor and associated dialogs, including a package-creation expert, have been added. The Palette options page (accessed by selecting Component | Configure Palette, or by selecting Tools | Options and clicking the Palette tab) now displays the package where each component resides.

Custom components are now installed in the IDE as packages.

 ExitProc should be used only when generating .EXE files. Do not use ExitProc within a dynamically loaded package, such as design-time code. All ExitProc calls are now made before any of the finalization sections are executed. Use finalization sections to implement exit behavior. In existing code, ExitProc variables should be moved to finalization sections.

 To display packages in IDE file-selector dialogs, you must have DLL file display enabled in Windows. To verify that DLL file display is enabled, open the Windows Explorer, choose View|Options, and click the View tab.

For more information on Packages:

Working with Packages

Why use packages?

Runtime packages

Using packages in an application

Deciding which runtime packages to use

Custom packages

Design-time packages

Installing component packages

Creating and editing packages

Creating a package

Editing an Existing Package

Understanding the Structure of a Package

Editing .DPK Files Manually

Compiling Packages

Inserting package-specific compiler directives in code

Using the command-line compiler

Package files created by a successful compilation

Deploying packages


Working with Packages

A package is a specially compiled dynamic-link library (DLL) used by Delphi applications, the Delphi IDE, or both. Runtime packages provide functionality when a user runs an application. Design-time packages are used to install components in Delphi's IDE and to create special property editors for custom components. A single package can function at both design time and runtime, and design-time packages frequently work by calling runtime packages.

Like all DLLs, packages contain code that can be shared among applications. For example, most of Delphi's commonly used components reside in a package called VCL30.DLL. Each time you create an application, you can specify that it uses VCL30. When you compile an application created this way, the application's executable image contains only the code and data unique to it; the common code is in VCL30.DLL. A computer with several package-enabled applications installed on it needs only a single copy of VCL30.DLL, which is shared by all the applications and the Delphi IDE itself.

Delphi ships with several precompiled runtime packages, including VCL30.DLL, that encapsulate new and previously-existing components. Delphi also uses special design-time packages that facilitate manipulation of components in the IDE.

You can build applications with or without packages. However, if you want to add custom components to the IDE, you must install them as design-time packages.

You can create your own packages to share among applications. For example, if you have several applications that process statistical data, you can create a package containing statistical functions and classes common to these applications.

Note:	Before working with packages, make sure that .DLL file display is enabled in Windows. Open the Windows Explorer, choose View|Options, and click the View tab. If .DLL files are marked as hidden, packages will not appear in IDE file-selection dialogs.


Why use packages?

Packages are optional, but they offer several advantages over conventional programming. By compiling reused code into a DLL, you can share it among applications. For example, all of your applications--including the Delphi IDE itself--can access standard components through DLLs. Since the applications don't have separate copies of the component library bound into their executables, the executables are much smaller. The result is a dramatic savings in both system resources and hard disk storage. Moreover, packages allow faster compilation because only code unique to the application is compiled with each build.


Runtime packages

Runtime packages are deployed with Delphi applications. They provide functionality when a user runs the application.

To run an application that uses packages, a computer must have both the application's .EXE file and all the packages (.DLL files) that the application uses. When you deploy an application, you must make sure that users have correct versions of any required packages.


Using packages in an application

To use packages in an application,

1.	Load or create a project in the IDE.

2.	Choose Project|Options.

3.	Choose the Packages tab.

4.	Select the "Build with Runtime Packages" check box, and enter one or more package names in the edit box underneath. To add a package to an existing list, click the Add button and enter the name of the new package in the Add Package dialog. To browse from a list of available packages, click the Add button, then click the ellipsis marks (...) next to the Package Name edit box in the Add Package dialog.

If you use the Search Path edit box in the Add Package dialog, you will be changing the global Library Path for Delphi.

You do not need to include file extensions with package names. If you type directly into the Runtime Packages edit box, be sure to separate multiple names with semicolons. For example:

VCL30;VCLDB30;VCLDBX30

Packages listed in the Runtime Packages edit box are automatically linked into your application when you compile; if the edit box is empty, the application is compiled without packages. Runtime packages are selected for the current project only. To make the current choices into automatic defaults for new projects, select the "Defaults" check box at the bottom of the dialog.

Note:	When you create an application with packages, you still need to include the names of the original Delphi units in the uses clause of your source files. For example, the source file for your main form may begin like this:

unit MainForm;

interface

uses

  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

Each of the units referenced in this example is contained in the VCL30 package. Nonetheless, you must keep these references in the uses clause, even if you use VCL30 in your application, or you will get compiler errors.


Deciding which runtime packages to use

Delphi ships with several precompiled runtime packages, including VCL30, which supply basic Object Pascal and VCL component support. In previous versions of Delphi the functionality provided by these packages was obtained by binding a complete copy of the component library into each application.

The VCL30 package contains the most commonly used components, system functions, and Windows interface elements. It does not include database or Windows 3.1 components, which are available in separate packages. The following table lists the runtime packages shipped with Delphi and the units they contain.

Package	Units

VCL30.DLL	Buttons, Classes, Clipbrd, Comctrls, Commctrl, Commdlg, Comstrs, Consts, Controls, Ddeman, Ddeml, Delphimm, Dialogs, Dlgs, Dsgnintf, Editintf, Extctrls, Forms, Graphics, Grids, Imm, IniFiles, Mapi, Masks, Math, Menu, Messages, Mmsystem, Mplayer, Ole2, Oleauto, Olecntrs, Olectl, Olectrls, Oledlg, Penwin, Printers, Registry, Regstr, Richedit, Shellapi, Shlobj, Stdctrls, System, SysUtils, Typinfo, Windows

VCLX30.DLL	Colorgrd, Diroutln, Filectrl, Gauges, Outline, Spin, Tabnotbk, Tabs

VCLDB30.DLL	Db, Dbcgrids, Dbctrls, Dbgrids, Dbinpreq, Dblogdlg, Dbpwdlg, Dbtables

VCLDBX30.DLL	Dblookup, Report

Suppose you want to create a client/server database application that uses packages. In this case, you need at least two runtime packages: VCL30 and VCLDB30. If you expect to incorporate ReportSmith into your application, then you also need VCLDBX30. To use these packages, choose Project|Options, select the Packages tab, and enter the following list in the Runtime Packages edit box.

VCL30;VCLDB30;VCLDBX30

Actually, you don't have to include VCL30 in this list, because VCL30 is referenced in the requires clause of VCLDB30. (See "The requires clause" ) Your application will compile just the same whether or not VCL30 is included in the list.


Custom packages

A custom package is either a DLL you code and compile yourself, or a precompiled package from a third-party vendor. To use a custom runtime package with an application, choose Project|Options and add the name of the package to the Runtime Packages edit box on the Packages page. For example, suppose you created a statistical package called STATS.DLL. To use it in an application, the line you enter in the Runtime Packages edit box might look like this:

VCL30;VCLDB30;STATS

If you create your own packages, you can add them to the list as needed.


Design-time packages

Design-time packages are used to install components on the IDE's Component palette and to create special property editors for custom components. Packages compiled with the {$DESIGNONLY ON} compiler directive are not available for use in applications.

Delphi ships with the following design-time component packages preinstalled in the IDE.

Package	Component palette pages

DCLSTD30.DLL	Standard, Additional, Win95, Dialogs, System

DCLDB30.DLL	Data Access, Data Controls

DCL31W30.DLL	Win 3.1

DCLQRT30.DLL	QReport

DCLNET30.DLL	Internet

VCLISP30.DLL	Internet

DCLSMP30.DLL	Samples

VCLOCX30.DLL	OCX

These design-time packages work by calling runtime packages, which they reference in their requires clauses. (See "The requires clause" .) For example, STDLIB references VCL30. STDLIB itself contains additional functionality that makes most of the standard components available on the Component palette.

In addition to preinstalled packages, you can install your own component packages, or component packages from third-party developers, in the IDE.


Installing component packages

In Delphi all components are installed in the IDE as packages. If you've written your own components, create and compile a package that contains them. (See "Creating and editing packages" below.) The unit file that contains your component source code must correspond to the model for component source files described in the Delphi Component Writer's Guide.

To install or uninstall your own components, or components from a third-party vendor, follow these steps:

1.	If you are installing a new package, copy or move the .DLL, .DCP, and .DCU files to a local directory. The directory where you store the .DCP file--and the .DCU files, if they are included with the distribution--must be in the Delphi Library Path. (For information about .DCP and .DCU files, see "Package files created by a successful compilation".) It is customary to put executable files in the Delphi \BIN directory.

2.	Choose Component|Install Packages from the IDE menu, or choose Project|Options and click the Packages tab.

3.	A list of available packages appears under "Design packages".

To install a package in the IDE, select the check box next to it.

To uninstall a package, deselect its check box.

To see a list of components included in an installed package, select the package and click Components.

To add a package to the list, click Add and browse in the Open Package dialog for the directory where the .DLL file resides (see step 1). Select the .DLL file and click Open.

To remove a package from the list, select the package and click Remove.

4.	Packages are installed in the IDE for the current project only. To make the current installation choices into automatic defaults for new projects, select the "Defaults" check box at the bottom of the dialog.

5.	Click OK.

To remove components from the Component palette without uninstalling a package, select Component|Configure Palette, or select Tools|Options and click on the Palette tab. The Palette options tab lists each installed component along with the name of the Component palette page where it appears. Selecting any component and clicking Hide removes the component from the palette.


Creating and editing packages

A package source file does not contain type, data, procedure, or function declarations. Instead it contains a name for the package, a list of other "required" packages, and pointers to the source-code units that should be bound into the package. Hence, creating a package involves specifying

A name for the package.

A list of other packages to be required by, or linked to, the new package.

A list of unit files to be contained by, or bound into, the package when it is compiled. The package is essentially a wrapper for these source-code units, which contain the functionality of the compiled DLL.

Package source files, which end with the .DPK extension, are generated by the Package editor.


Creating a package

To create a package, follow the procedure below. Refer to "Understanding the Structure of a Package" for more information about the steps outlined here.

1.	Load or create a project in the IDE.

2.	Choose File|New, select the Package icon, and click OK.

3.	In the New Package dialog, enter a file name and (optionally) a description. You can include a directory path with the name; otherwise, the package will reside in the current directory.

4.	Click OK to open the Package editor.

5.	Choose the Contains tab, then click the Add Unit speed button to add a unit file to the package's contains clause.

6.	When the Add Unit dialog opens, you can add an existing unit file to the package, create a new component that descends from an existing component, or import an ActiveX component.

To add a unit file, choose the Add File tab and enter a file in the Unit File Name edit box. You can specify an entire directory path with the file name, or edit the search path in the Search Path edit box; if you edit the search path, you will be changing the global Library Path for Delphi. To add more unit files to the package, close the Add Unit dialog by clicking OK, then click the Add Unit speed button again in the Package editor.

To create a new component, choose the Create Component tab. Enter the new component's class name, the name its immediate ancestor, the name of the unit file where the component's declaration should be written, and the page on the Component palette where you want the new component displayed. You can specify an entire directory path with the file name, or edit the search path in the Search Path edit box; if you edit the search path, you will be changing the global Library Path for Delphi.

To import an ActiveX component, choose the Import ActiveX tab and select a registered component from the list box. To register a new component, click Add. To unregister a component, click Remove.

7.	In the Package editor, choose the Requires tab to see a list of other packages in the requires clause of the package you are editing. VCL30 is listed by default. If you want to add more packages to the requires clause, click the Add Package speed button and proceed as in step 6 above.

8.	Click the Package Options speed button and select the Attributes tab.

Under "Usage options", check "Design package", "Runtime package", or both. Selecting only one of these options turns on the $RUNONLY or $DESIGNONLY compiler directive. (See "Inserting package-specific compiler directives in code".) To create a design-time package that is not installable in the IDE (but may still be called by other design-time packages), deselect both check boxes.

Under "Build control", select "Rebuild as needed" or "Explicit rebuild". "Explicit rebuild" turns on the -Z compiler switch, which prevents implicit recompilation of the package. (See "Using the command-line compiler".)

9.	Click the Build/Save speed button to compile your package.


Editing an Existing Package

To edit an existing package in the Package editor, choose File|Open, select "Delphi Package (*.dll)" from the "Files of type" drop-down list, and select a .DLL file.

The Contains page in the Package editor displays a list of unit files contained in the current package. The Requires page displays a list of other packages required by (linked to) the current package. You can add to or delete from either list by clicking the appropriate speed button.

The View speed button opens the selected unit file or package in the Code or Package editor. The Package Options speed button lets you edit the package's description or set compiler and library options.


Understanding the Structure of a Package

Naming Packages

Package names must be unique within a project. If you name a package "STATS", the Package editor generates a source file for it called "STATS.DPK"; the compiler generates an executable and a binary image called "STATS.DLL" and "STATS.DCP", respectively. Use "STATS" to refer to the package in the requires clause of another package, or when using the package in an application.

The requires clause

The requires clause lists other, external packages that are used by the current package. It functions like the uses clause in a Delphi unit file. If the unit files contained in your package make references to other packages, you must include those packages in your package's requires clause. A package listed in the requires clause of another package is automatically linked at compile time into any application that uses the other package.

The -L compiler switch allows you to list additional required packages on the command line. For details, see "Using the command-line compiler".

Note:	Most packages that you create will require VCL30. Any package that depends on VCL units (including SysUtils) must list VCL30, or another package that requires VCL30, in its requires clause.

 Avoiding circular package references

Packages cannot contain circular references in their requires clauses. This means that

A package cannot reference itself in its own requires clause.

A chain of references must terminate without rereferencing any package in the chain. If package A requires package B, then package B cannot require package A; if package A requires package B and package B requires package C, then package C cannot require package A.

 Handling duplicate package references

Duplicate references in a package's requires clause--or in the Runtime Packages edit box--are ignored by the compiler. For programming clarity and readability, however, you should catch and remove duplicate package references.

The contains clause

The contains clause identifies the unit files (.PAS or .DCU) to be bound into the package. If you are writing your own package, put your source code in .PAS files and list them in the contains clause. Do not include file-name extensions in the list; units are recompiled only if their .DCU files cannot be found or their .PAS files have changed since the last build.

 Avoiding redundant source code uses

A package cannot be listed in the contains clause of another package or the uses clause of a unit.

All units included directly in a package's contains clause, or indirectly in the uses clauses of those units, are bound into the package at compile time. The units contained (directly or indirectly) in a package cannot be contained in any other packages referenced in requires clause of that package.

A unit cannot be contained (directly or indirectly) in more than one package used by the same application, including the Delphi IDE. To use an already-packaged unit file in another package, list the first package in the second package's requires clause.


Editing .DPK Files Manually

Package source files, like project files, are generated by Delphi from information you supply. Like project files, they can also be edited manually. A package source file should be saved with the .DPK (Delphi package) extension to avoid confusion with other files containing Object Pascal source code.

The Package editor ordinarily discards generated .DPK files after compilation. To save generated .DPK files, choose Tools|Options and select the Library tab (or click the Package Options speed button in the Package editor and select the Build tab), then select the "Save library source code" check box.

The following diagram summarizes the syntax for a package source file.

For example, the following code declares the VCLDB30 package.

package VCLDB30;

requires VCL30;

contains Db, Dbcgrids, Dbctrls, Dbgrids, Dbinpreq, Dblogdlg, Dbpwdlg, Dbtables;

end.

The package heading specifies the name for the package.

The requires clause lists other, external packages used by the current package. If a package does not reference other packages, then it does not need a requires clause.

The contains clause identifies the unit files to be compiled and bound into the package.


Compiling Packages

You can compile a package from the IDE or from the command line. When you rebuild any project that contains a package, the package is implicitly recompiled if necessary. To recompile a package by itself from the IDE,

1.	Choose File|Open.

2.	Select "Delphi Package (*.dll)" from the "Files of type" drop-down list.

3.	Select a .DLL file in the dialog.

4.	When the Package editor opens, click the Build/Save speed button.

To select a .DPK file for compilation, you must use the command line.

You can insert compiler directives into your package source code. For more information, see "Inserting package-specific compiler directives in code".

If you compile from the command line, new package-specific switches are available. For more information, see "Using the command-line compiler".


Inserting package-specific compiler directives in code

The following table lists package-specific compiler directives that you can insert into your source code.

Directive	Purpose

{$G-} or {IMPORTEDDATA OFF}	Disables creation of imported data references. Using this directive increases memory-access efficiency, but prevents packaging of unit.

{$WEAKPACKAGEUNIT ON}	Packages unit "weakly". See "The $WEAKPACKAGEUNIT directive".

{$DENYPACKAGEUNIT ON}	Prevents unit from being placed in a package.

{$DESIGNONLY ON}	Compiles the package as design-time only. (Put in .DPK file.)

{$RUNONLY ON}	Compiles the package as runtime only. (Put in .DPK file. Available with command-line compiler only.)

Note:	Including {$G-}, {IMPORTEDDATA OFF}, or {$DENYPACKAGEUNIT ON} in your source code prevents the unit file from being packaged. Other compiler directives may be included, if appropriate, in package source code. See the Object Pascal Language Guide for information on compiler directives not discussed here.

 The $WEAKPACKAGEUNIT directive

The $WEAKPACKAGEUNIT directive affects the way a .DCU file is stored in a package's .DCP and .DLL files. (For information about files generated by the compiler, see "Package files created by a successful compilation".) If {$WEAKPACKAGEUNIT ON} appears in a unit file, the compiler omits the unit from DLLs when possible, and creates a non-packaged local copy of the unit when it is required by another application or package. A unit compiled with this directive is said to be "weakly packaged".

For example, suppose you've created a package called PACK that contains only one unit, UNIT1. Suppose UNIT1 does not use any further units, but it makes calls to RARE.DLL. If you put {$WEAKPACKAGEUNIT ON} in UNIT1.PAS when you compile your package, UNIT1 will not be included in PACK.DLL; you will not have to distribute copies of RARE.DLL with PACK. However, UNIT1 will still be included in PACK.DCP. If UNIT1 is referenced by another package or application that uses PACK, it will be copied from PACK.DCP and compiled directly into the project.

Now suppose you add a second unit, UNIT2, to PACK. Suppose that UNIT2 uses UNIT1. This time, even if you compile PACK with {$WEAKPACKAGEUNIT ON} in UNIT1.PAS, the compiler will include UNIT1 in PACK.DLL. But other packages or applications that reference UNIT1 will use the (non-packaged) copy taken from PACK.DCP.

Note:	Unit files containing the {$WEAKPACKAGEUNIT ON} directive must not have global variables, initialization sections, or finalization sections.

The $WEAKPACKAGEUNIT directive is an advanced feature intended for developers who distribute their DLLs to other Delphi users. It can help you to avoid distribution of infrequently used DLLs, and to eliminate conflicts among packages that may depend on the same external DLL.

For example, Delphi's PenWin unit references PENWIN.DLL. Most projects don't use PenWin, and most computers don't have PENWIN.DLL installed on them. For this reason, the PenWin unit is weakly packaged in VCL30. When you compile a project that uses PenWin and the VCL30 package, PenWin is copied from VCL30.DCP and bound directly into your project; the resulting executable (.EXE or .DLL) is statically linked to PENWIN.DLL.

If PenWin were not weakly packaged, two problems would arise. First, VCL30 itself would be statically linked to PENWIN.DLL, and so you could not load it on any computer which didn't have PENWIN.DLL installed. Second, if you tried to create a package that contains PenWin, a compiler error would result because the PenWin unit would be contained in both VCL30 and your package. Thus, without weak packaging, PenWin could not be included in standard distributions of VCL30.


Using the command-line compiler

When you compile from the command line, you can use the package-specific switches listed in the following table.

Switch	Purpose

-$G-	Disables creation of imported data references. Using this switch increases memory-access efficiency, but prevents packaging of unit.

-L <packagename>[;<packagename>...]	Lists additional packages required by the package being compiled.

-Z	Prevents a package from being implicitly recompiled later. Use when compiling packages that provide low-level functionality, that change infrequently between builds, or whose source code will not be distributed.

Note:	Using the -$G- switch prevents the unit file from being packaged. Other command-line options may be used, if appropriate, when compiling packages. See the Object Pascal Language Guide for information on command-line options not discussed here.


Package files created by a successful compilation

To create a package you compile a source file that has a .DPK extension. The base name of the .DPK file becomes the base name of the files generated by the compiler. For example, if you compile a package source file called TRAYPAK.DPK, the compiler creates a package called TRAYPAK.DLL.

The following table lists the files produced by the successful compilation of a package.

File extension	Contents

DCP	A binary image containing a package header and the concatenation of all DCU files in the package. A single DCP file is created for each package. The base name for the DCP is the base name of the DPK source file.

DCU	A binary image for a unit file contained in a package. One DCU is created, when necessary, for each unit file.

DLL	The runtime package. This file is a normal Windows DLL with special Delphi-specific features. The base name for the DLL is the base name of the DPK source file.

If you use the Package editor to build a package, the resulting .DPK file will be discarded after compilation unless the "Save library source code" check box is selected on the Library options page. (To access the Library options page, choose Tools|Options and select the Library tab, or click the Package Options speed button in the Package editor and select the Build tab.)


Deploying packages

If you distribute runtime or design-time packages to other Delphi developers, be sure to supply both .DCP and .DLL files. You will probably want to include .DCU files as well.

When distributing an application that uses runtime packages, make sure that your users have the application's .EXE file as well as all the .DLL files that the application calls. If the .DLL files are in a different directory from the .EXE file, they must be accessible through the user's Path. You may want to follow the convention of putting .DLL files in the Windows\System directory. If you use InstallShield Express, your installation script can check the user's system for any packages it requires before blindly reinstalling them.


Using Distributed Datasets

This Ivory field test includes rudimentary support for a new concept: distributed datasets. Distributed datasets look and behave like the fully functional datasets encapsulated by the TTable, TQuery, and TStoredProc component, but they are fundamentally different from them.

In the standard dataset model, a dataset component provides a direct link to a database table through the Borland Database Engine (BDE). The entire functionality of the BDE is encapsulated in each client application. Data is downloaded to the application as needed, and uploaded to the database. Data transfers are governed by implicit or explicit transactions. If cached updates are enabled, data is downloaded to the application as needed, updates are performed against the local copy of the data, and then these updates are written en masse to the database by an explicit call to the dataset's ApplyUpdates method.

In the distributed dataset model, the actual dataset resides on an application server rather than on the client machine. Two server objects, TBDEProvider and TBDEResolver, provide a dataset interface for all potential clients. A new dataset component, TMemoryDataset gains access to a dataset on the application server by linking to the appropriate provider and resolver objects on the server. TMemoryDataset maintains an in-memory, local copy of data, and the client applies insertions, updates, and deletions against the local copy of the data. To update the remote dataset, the client returns only those records that have changed. The remote dataset applies those deltas to the database. Figure 1.1 illustrates the basic model for distributed datasets.

 Distributed dataset model:

The distributed dataset model offers the following advantages to client/server developers:

Thin clients that provide BDE dataset access while maintaining a small executable footprint.

Centralized database application servers that act as data brokers and that can provide a corporate-wise set of business rules.

N-tier database development

The following sections describe how to create and set up a database application server and thin client applications:

Creating a database application server

Creating a server without a provider and resolver

Creating a server with an explicit provider and resolver

Adjusting provider behavior

Setting the maximum data block size

Adjusting resolver behavior

Creating a thin client

Displaying data from a memory dataset

Editing data associated with a memory dataset


Creating a database application server

In general you build a database application server very much like you build a standard Ivory database client application. A database application server, however, enables other--client--applications to access its datasets through two special intermediate agents. The TBDEProvider receives client requests for server data, and provides the data to them. The TBDEResolver receives updated data from the client, applies it to the server data, and reports update errors, if any, to the client.

When you create a database application server, you can place providers and resolvers at design-time, or you can let the server create them on the fly as needed at run time. When you place providers and resolvers at design-time, you gain greater control over them because you can set their properties and write event handlers (for example, to handle exceptions) for them.


Creating a server without a provider and resolver

Create a server without an explicit provider and resolver only when your application server does not need to modify the default property settings for them, and when it does not need to handle exceptions such as when updates cannot be applied to a dataset.

To create a database application server without an explicit provider and resolver:

Open a new data module in the application server project.

Place a TQuery, TTable, or TStoredProc component on the data module.

Set the DatabaseName property for the dataset component to the desired BDE alias, and set the TableName (TTable), SQL (TQuery), or StoredProcName (TStoredProc) property to generate the desired dataset.

A client memory dataset accesses the server's dataset directly through the Data property. For example:

MemoryDataSet1.Data := Table1.Data;

When the dataset on the server (Table1) receives this assignment request at run time, it automatically generates a TBDEProvider component to handle the data transfer. Similarly, a client memory dataset's Delta property is used to pass data changes back to the server's dataset through the Data property:

Table1.Data := MemoryDataSet1.Delta;

When the server receives this assignment request, it automatically generates a TBDEResolver component to handle the data modifications and report errors, if any, to the client.


Creating a server with an explicit provider and resolver

In most cases, developers will want to create explicit providers and resolvers at design time in order to control data communication and handle error conditions. For example, when a client requests data, the provider's BeforeGetData event is triggered. Such an event might verify that the dataset is open and that the requested data is available before processing the request. Similarly when a client passes updates to the server and the server cannot carry them out, the server reports errors to the client. If a developer creates an explicit resolver, then the developer can also create an OnUpdateError event handler for the resolver that may be able to resolve most error conditions without having to pass them back to the client.

To create a database application server with an explicit provider and resolver:

1.	Open a new data module in the application server project.

2.	Place a TQuery, TTable, or TStoredProc component on the data module.

3.	Set the DatabaseName property for the dataset component to the desired BDE alias, and set the TableName (TTable), SQL (TQuery), or StoredProcName (TStoredProc) property to generate the desired dataset.

4.	Place a TBDEProvider component on the data module, and set its SourceDataSet property to the name of the dataset component placed in Step 2.

5.	Place a TBDEResolver component on the data module, and set its SourceDataSet property to the name of the dataset component placed in Step 2.

 Other options for specifying sources are presented in subsequent discussions below.

A client memory dataset accesses a server's data through the provider's the GetData function. For example:

MemoryDataSet1.Data := BDEProvider1.GetData;

GetData calls the BeforeGetData event handler, processes the data request and passes the results to the client, and then calls the AfterGetData event handler. Similarly, a client memory dataset's uses its Delta property to pass data changes to the server's resolver through the resolver's ApplyUpdates function:

BDEResolver1.ApplyUpdates(Table1.Delta);

ApplyUpdates:

Calls its BeforeUpdate event handler, if one is defined.

Sets a callback pointer to its OnUpdateError event handler if one is defined.

Applies the updates to its dataset, (processing update errors if the OnUpdateError event handler is defined).

Calls its AfterUpdate event handler, if one is defined.

Returns any records that generated errors to the client.


Adjusting provider behavior

You can optionally modify properties and write event handlers for a TBDEProvider component in order to refine its behavior to suite the needs of your work environment.


Setting the maximum data block size

The MaxDataSize property specifies the maximum size of each data block to transfer between the provider and its clients. When MaxDataSize is -1 (the default) block sizes are unlimited. The minimum value for MaxDataSize is 4096. Values greater than -1 and less than 4096 are automatically adjusted to the minimum value of 4096.

The smaller the block size, the more data blocks may be required to pass data to the client.

Setting the maximum number of rows to transfer

The MaxRows property specifies the maximum number of records to transfer to the client. When MaxRows is -1 (the default), the provider sends all requested records to the client.

In some environments where many applications share access to the same databases, there may be a need to restrict the number of records on an application-by-application basis. In these cases, set MaxRows to a positive value to specify how many records to transfer.

Enabling server constraints

The Constraints property controls whether or not the constraints defined for the database table or tables underlying a database application server's dataset components are applied to data requests. When Constraints is True (the default), constraints are applied. To disable constraints, set Constraints to False.

Note:

Constraints is inoperative for this field test.

Creating data request event handlers

Two possible events can occur for a TBDEProvider if you write handlers for them. BeforeGetData is triggered when a provider receives a data request from a client. AfterGetData is triggered after the provider passes requested data to the client. For example, you might create a BeforeGetData event handler to retrieve a database password, and then create an AfterGetData event to remove the password from memory.


Adjusting resolver behavior

You can optionally modify properties and write event handlers for a TBDEResolver component in order to refine its behavior to suite the needs of your work environment.

Specifying a resolver's data source

A resolver's data source specifies the data it attempts to update when it receives an update request from a client. There are four properties that can specify a resolver's data source in one of three mutually exclusive ways:

SourceDataSet specifies a dataset component that belongs to the database server application. At design time you can choose from available dataset components in the SourceDataSet drop-down list in the Object Inspector. This is the most common option used to specify a data source for the resolver.

 If SourceDataSet is specified, any values previously assigned to the DatabaseName, SourceTableName, and SourceSQL properties are cleared.

SourceTableName and DatabaseName are used together to specify a BDE alias and a table in the database referenced by the alias. The main use for these options is to enable a resolver to write changes to a different database from the one used by the provider.

 If these properties are set, any values previously assigned to the SourceDataSet and SourceSQL properties are cleared.

SourceSQL and DatabaseName are used together to specify the SQL statement that generated the data originally passed to the client.

 If SourceSQL is specified, any values previously assigned to SourceDataSet and SourceTableName are cleared.

Aborting updates on error conditions

To cancel all updates immediately when a database server error occurs during updating, set the Abort property to True. By default, Abort is False, meaning that when a server error occurs, any record that causes an error is cached and updating continues. When all records are processed, records that caused errors are returned to the client.

Specifying how to match records for updating

The UpdateMode property enables an application to specify whether SQL-based updates should be performed against all records (dsupWhereAll), or only for those that match the current index for the query (dsupWhereKey).

Note:	 For this field test UpdateMode is always dsupWhereAll even if an application sets UpdateMode to dsupWhereKey.

Creating update event handlers

Three possible events can occur for a TBDEResolver if you write handlers for them. BeforeUpdate is triggered when a resolver receives an update request from a client. AfterUpdate is triggered after the resolver completes an update request, but before it passes any unresolved records back to the client.

OnUpdateError is triggered whenever an error occurs during the actual update process For example, if the server rejects a record for update because it violates a constraint, such as a value that is out of bounds, the OnUpdateError event handler is called if it is defined. OnUpdateError can be used to correct an error an retry the update.

For a full discussion of writing an OnUpdateError handler, see the Chapter 15: "Working with cached updates" in the 2.0 Database Application Developer's Guide.


Creating a thin client

In general you build a thin client very much like you build a standard Ivory database client, except that all dataset components in the thin client are TMemoryDataSet components instead of TTable, TQuery, or TStoredProc components. Unlike these latter components, all TMemoryDataSet components in a client share a DLL to maintain BDE compatibility, so the client's footprint remains small.

A TMemoryDataSet component gets its data from the Data property. Data is a variant, and is a run time only property. At run time a client memory dataset accesses a database application server's data through the GetData function of the server's TBDEProvider. For example:

MemoryDataSet1.Data := BDEProvider1.GetData;


Displaying data from a memory dataset

To display data from a memory dataset in a data-aware control:

Place a data source component on a form or data module and set its DataSet property to the name of the memory dataset.

Place a control on a form, make sure the form uses the unit that contains the data source component, and set the control's DataSource property to the name of the data source component.

Code a request for data, for example, as an OnClick event for a button.

Compile and run the application.

Note:	In this field test you can populate a memory dataset at design time if the provider, resolver, and their source dataset component also reside on a form in the test client application. To populate the memory dataset and display data in controls at design time, right-click the dataset component and select Define Data. Select a source dataset. Invoke the Fields editor for the dataset to create persistent fields for it.


Editing data associated with a memory dataset

To edit data associated with a memory dataset, enter changes in a data-aware control and post the changes. Data changes are automatically written to a memory dataset's Delta property. Delta is also a variant, and like the Data property, is only available at run time. Changes written to Delta are cached until an application calls the ApplyUpdates method for the servers's TBDEResolver to request that the changes be written to the dataset on the server.

ApplyUpdates takes a variant as a parameter. Pass the memory dataset's Delta property for this parameter. ApplyUpdates also returns a variant. If all updates are successfull applied to the dataset, then the return value is nulll. Otherwise one or more updates could not be applied to the dataset. The client application should always examine the return value and call its Reconcile method to clear updates that were successful and handle the updates that caused errors. If a developer writes an OnReconcileError event handler, the Reconcile method calls the event handler for each record it cannot reconcile.

The following code illustrates how a client might call a resolver's ApplyUpdates method within the context of a call to a memory dataset's Reconcile method:

begin

  .

  .

  .

  with MemoryDataSet1 do

    Reconcile(BDEResolver1.ApplyUpdates(Delta));

  .

  .

  .

end;


Introduction

Ivory provides new classes that implement COM interfaces. COM is the Component Object Model, an object-based programming model designed to provide language-independent application communication through a set of pre-defined routines. Interfaces are the encapsulation of these COM routines. As part of the COM specification, interfaces provide a means for developers to create consistently extensible components that can interact with other COM-based API components on the system.


What is an interface?

An interface is a named group of semantically-related, abstract methods that encapsulate the functionality for a particular, logically-defined behavior. Technically an interface is a pointer to a virtual method table and a guarantee of specific functionality. For example, the IMalloc interface allocates, frees, and manages memory. Similarly, the IPersist interface is the base interface for three other persistence-related interfaces that provide method prototypes for loading and saving the state of an object to a storage, stream, or file.

As a Delphi type, interfaces are similar to, but not the same as, class types. The declaration of an interface does not imply an actual implementation of the interface. Interfaces are never implemented by themselves, but are always implemented as part of a class. With regard to the interface, this implementation is completely hidden and fully polymorphic as long as the implementation conforms to the interface definition.

An interface reference is a reference to an implementation of an interface. Through an interface reference it is possible to invoke methods of a given interface without any knowledge of the actual implementation of the interface. For more information about implementing interfaces, see the Object Pascal Language Guide..


A binary standard

COM was designed to provide robust component interoperablity between binary executables, and is based upon the definition of a binary standard. This binary standard is used for the layout of the virtual method table (v-table) for an interface and for the communication process between COM objects specifed by function pointers. By using a binary standard, rather than a source code standard, COM establishes a protocol for accessing objects that is both platform extensible and language-independent.

All interface types are implicity derived from IUnknown, just as all class types are derived from TObject. In Delphi the IUnknown standard interface is declared by the System unit as follows:

type

  IUnknown = interface

    ['{00000000-0000-0000-C000-000000000046}']

    function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;

    function _AddRef: Integer; stdcall;

    function _Release: Integer; stdcall;

  end;

The layout of the v-table for any interface defines the first three methods and the order in which they must exist. These are the methods of IUnknown, which comprise the essential functionality of an interface to provide access to the interfaces implemented by the class and to manage the lifetime of the object. Every class that implements interfaces must implement the three IUnknown methods: QueryInterface, AddRef, and Release.

The first function in the v-table for every interface, is always QueryInterface. QueryInterface provides clients with a way for a given object to query and access pointers to the interfaces supported by other COM objects. As the first entry in the v-table, QueryInterface is a memory-efficient way of determining the capabilities of an object. It also makes it possible to update a COM object without the overhead of updating other dependent objects. For more information about how QueryInterface is implemented in Delphi, see the Object Pascal Language Guide.

The second and third methods in the v-table for every interface are AddRef and Release. These are simple reference counting methods that provide a way for a COM object to track how many references to it exist. While the reference count is non-zero the object must remain in memory. Once the reference count reaches zero the interface implementation can safely dispose of the underlying objects(s). These methods thereby provide the life-time management of COM objects.

The methods of any interface always consist of the three methods declared by IUnknown, the methods declared by each ancestor interface, and the methods declared by the interface itself. However many methods an interface contains, by using a binary standard and defining the basic behavior of any interface, COM offers a programming environment wherein objects written in different languages can communicate simply by providing a pointer to an interface function table.


New COM classes

Ivory provides new classes that implement COM interfaces. Using these COM classes provides a means for developers to create consistently extensible components that can interact with other COM-based API components on the system.

The following is an indented list illustrating the new classes in the hierarchy. Note that most are derived directly and indirectly from TObject, thereby minimizing the overhead for objects that do use interfaces. Existing Delphi classes can be used as a base for users to create classes that selectively implement interfaces for creating COM objects and ActiveX controls.

TObject

TAggregatedObject

TConnectionPoints (IConnectionPointContainer)

TContainedObject (IUnknown)

TConnectionPoint (IConnectionPoint)

TComClassManager

TComObject (IUnknown, ISupportErrorInfo)

TActiveFormContainer ( IPersistStreamInit, IOleObject, IOleControl, IOleInPlaceObject, IOleInPlaceActiveObject, IViewObject, IViewObject2)

TActiveXPropertyPage ( IPropertyPage, IPropertyPage2)

TTypedComObject (IProvideClassInfo)

TAutoObject (IDispatch)

TActiveXControl (IPersistStreamInit, IPersistStorage, IPersistPropertyBag, IOleObject, IOleControl, IOleInPlaceObject, IOleInPlaceActiveObject, IViewObject, IViewObject2, IPerPropertyBrowsing, ISpecifyPropertyPages)

TComObjectFactory (IClassFactory)

TActiveFormFactory

TActiveXPropertyPageFactory

TTypedComObjectFactory

TAutoObjectFactory

TActiveXControlFactory

TComponentFactory

TComServerObject

TComServer

TInterfacedObject (IUnknown)

TFontAdapter (IFontAdapter, IFontAccess)

TFontNotifier (IPropertyNotifySink)

TOleForm (IOleForm, IOleWindow, IOleInPlaceUIWindow, IOleInPlaceFrame, IVCLFrameForm)s

New OLE classes derived from Exception:

EOleCtrlError

EOleError

EOleSysError

EOleExceptions

New OLE helper classes derived from existing Delphi classes:

TPropertyPage (derived from TForm)

TActiveForm (derived from TForm)

TOleStream (derived from TStream)

For a more detailed discussion of some of the new classes and what interface or interfaces they implement, if any, see the Delphi Reference section on that specific class. For a general discussion of the features in the new classes, and what application models each addresses, see "Why use interfaces in Delphi?".

New units and unit inter-dependencies

The ComObj and ComServ units in the RTL\SYS directory and the AX, AXForm, and VCLCOM units in the VCL directory are available for testing. Additionally, the Ole2I unit in the RTL\WIN directory contains interface declarations and types needed to create classes that implement interfaces. Be sure to include this unit in the Uses clause for all files that use these declarations.

Note:

The OLE2I and AX units may be renamed before final product release.


Why use interfaces in Delphi?

Delphi defines a simple class, TInterfacedObject, that can be used as a base class when implementing interfaces in COM-compatable classes. These types of classes are not technically COM classes, which require a registered CLSID and the ability to be externally instantiated with a class factory. The following sections provide an overview of TInterfacedObject, and a sample of code demonstrating how to use interfaces in objects that are not COM classes.


TInterfacedObject as a base class

Because every interface inherits from IUnknown, an object that implements interfaces must implement the QueryInterface, _AddRef, and _Release methods defined by IUnknown. The simplest new class used for working with interfaces is TInterfacedObject. Because it implements IUnknown methods, TInterfacedObject serves as a convenient base class for creating interfaced objects.

The TInterfacedObject class is declared in the System unit as follows:

type

  TInterfacedObject = class(TObject, IUnknown)

  private

    FRefCount: Integer;

 protected

    function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;

    function _AddRef: Integer; stdcall;

    function _Release: Integer; stdcall;

  public

    property RefCount: Integer read FRefCount;

  end;

Deriving directly from TInterfacedObject is straightforward. In the following example declaration, TDerived is a direct descendent of TInterfacedObject and implements the IPaint interface.

type

  TDerived = class(TInterfacedObject, IPaint)

    ...

  end;

TInterfacedObject, or any of the new classes that implement interfaces, have the advantage of dynamic-binding providing true polymorphism on the interface. For example, given the following declarations:

var

  X: IPaint;

  P: TInterfacedObject;

begin

  X := P;

the variable P of type TInterfacedObject, can be assigned to the variable X, which is an IPaint interface reference. Dynamic binding makes this assignment possible. For this assignment, the compiler generates code to call the QueryInterface method of P's IUnknown interface, because the complier cannot tell from P's declared type whether P's instance actual supports IPaint. At run-time P resolves to an IPaint reference because of the call to QueryInterface, implemented by TInterfacedObject. P can then be assigned to X without error.

When VCL classes that do not implement interfaces are used as a base class for a class that implements interfaces, such an assignment will cause a compile-time error. For example:

var

  X: IPaint;

  P: TObject;

begin

  X := P;

will produce a compile error because P's declared type implements neither IUnknown or IPaint. In this case the IPaint interface has to be statically resolvable at compile-time because the QueryInterface method is not available in the base class.

For more information about such assignment rules regarding interfaces, see the Object Pascal Language Guide.

Using even simple interfaced classes, like TInterfacedObject, provides one of the basic advantages of using interfaces in an application, which is a clean way to handle versioning. Since QueryInterface is the first function in the vtable, an interface object can be dynamically updated without the overhead of updating other dependent objects. By using GUIDs rather than names to identify interfaces, classes that implement interfaces can support both old and new versions of an interface as a collection of methods and implementations. New interfaces can modify or add methods without a conflict of versions, which is a common problem when using DLLs. This is because interfaces are accessed by unique identifiers rather than by name, which ensures that calls to methods of the same name in different versions of an interface, never conflict.


Sample code using interfaces

Interfaces allow you to separate how a class can be used from how it is implemented.  Two classes can share the same interface without being required to descend from the same base class.   This polymorphic invocation of methods on unrelated object as long as the objects implement the same interface.  For example, consider the interface,

IPaint = interface

  procedure Paint;

end;

and the two classes,

TSquare = class(TInterfacedObject, IPaint)

  procedure Paint;

end;

TCircle = class(TInterfacedObject, IPaint)

  procedure Paint;

end;

Note that the two classes do not share a common ancestor, however, they are both assignment compatible with a variable of IPaint as in,

var

  Painter: IPaint;

begin

  Painter := TSquare.Create;

  Painter.Paint;

  Painter := TCircle.Create;

  Painter.Paint;

end;

This could have been accomplished by having TCircle and TSquare descend from say, TShape which implemented a virtual method Paint, and where both TCircle and TSquare would then have overriden the Paint method.  The above IPaint would be replaced by TShape. However, consider the following the following interface,

IRotate = class

  procedure Rotate(Degrees: Integer);

end;

which makes sence for the rectangle to support but not the circle.  The classes would look like,

TSquare = class(TInterfacedObject, IPaint, IRotate)

  procedure Paint;

  procedure Rotate(Degrees: Integer);

end;

TCircle = class(TInterfacedObject, IPaint)

  procedure Paint;

end;

Later, you could create a class TFilledCircle that implements the IRotate interface to allow rotation of a pattern used to fill the circle without having to add rotation to the simple circle.

Interfaces also allow you to write procedures that can generically handle objects without having to require the object to descend from a particular base class.  Using the above IPaint and IRotate interfaces you can write the following procedures,

procedure PaintObjects(Painters: array of IPaint);

var

  I: Integer;

begin

  for I := Low(Painters) to High(Painters) do

    Painters[I].Paint;

end;

procedure RotateObjects(Degrees: Integer; Rotaters: array of IRotate);

var

  I: Integer;

begin

  for I := Low(Rotaters) to High(Rotaters) do

    Rotaters[I].Rotate(Degrees);

end;

RotateObjects does not require that the objects know how to paint themselves and PaintObjects does not require the objects know how to rotate.   This allows the above objects to be used often than if they where written to only work against a TShape class.

Interfaces also allow the implementation of other generic algorithms such as sorting.  The following introduces an ICompare interface and a QuickSort procedure that uses the ICompare interface to sort an array of TObjects.

ICompare = interface

  function CompareWith(Object: TObject): Integer;

end;

...

procedure QuickSort(Objects: array of TObject);

  procedure Sort(Objects: array of TObject; Left, Right: Integer);

  var

    I, J: Integer;

    Object, Temp: TObject;

    Compare: ICompare;

  begin

    repeat

I := Left;

J := Right;

Object := Objects[(Left + Right) div 2];

Compare := Object as ICompare;

repeat

  while Compare.CompareWith(Objects[I]) < 0 do Inc(I);

  while Compare.CompareWith(Objects[J]) > 0 do Inc(J);

  if I <= J then

  begin

    Temp := Objects[I];

    Objects[I] := Objects[J];

    Objects[J] := Temp;

    Inc(I);

    Inc(J);

  end;

      until I > J;

if Left < J then Sort(Objects, Left, J);

Left := I;

    until I >= Right;

  end;

begin

  Sort(Objects, Low(Objects), High(Objects));

end;

The above routine could have been implemented without interfaces but would have required all objects to contain a CompareWith method introduced in a common ancestor.  This is not always possible.  You might not want to, or have the access to,  change the ancestors of all the classes you wish to sort.  Interfaces allow you to create descendent classes for the classes you wish to support that all implement ICompare without requiring the their ancestors to be modified.


COM and OLE classes in Delphi

Delphi COM classes enable users to more easily create COM-based client/server applications, such as OLE document servers and ActiveX controls. They also make it easier to use COM objects in client applications, bringing system features such as the Win95 Shell extensions and DirectX multimedia support within reach of Delphi programmers at all skill levels.

Delphi COM classes implement the COM and OLE standard interfaces that support these object types. Others helper classes, like the COM server and property pages classes, provide the implementation for objects that manage the mechanics of enabling and working with a COM application.

Different COM classes address different programming needs. The following overview of the Delphi COM classes is divided into categories meant to clarify the appropriate programming paradigm or application model for each class or group of classes. These categories are based upon the set of interfaces implemented by the classes in it, since the intention of interfaces is to define a specific purpose and use.


The basic COM object class

TComObject is used for creating COM objects that:

Support aggregation.

Are registered and can be externally instantiated using a class factory object.

Support OLE exception handling.

Support the safecall calling convention used for dual interfaces.

TComObject has a Controller property that can represent a controlling IUnknown for the COM object, indicating whether the instance is part of an aggregate. It deploys handling of the IUnknown methods accordingly.

TAggregatedObject and TContainedObject also implement aggregation, but with the specific purpose of supporting connectable objects used with ActiveX controls.

For more information about aggregation, see Kraig Brockschmidt's Inside Ole, second edition.

Because TComObject has a class factory, TComObjectFactory, instances of this class can be created through its class factory object. Class factories use unique class identifiers called CLSIDs that are a specific type of GUID. Classes are registered in the registry database according to their CLSID. For more information about what class factories offer, see "Class Factories and class instantiation".

Because TComObject supports the ISupportErrorInfo interface, objects instantiated from this class support OLE exception-handling and the safecall calling convention used for dual interfaces.


Classes and type information

TTypedComObject supports the IProvideClassInfo interface, which means that when instantiated, an object of this type can provide type information on that object without the client having to obtain a type library.

Using TTypedComObject requires a type library.


Class Factories and class instantiation

A class factory is responsible for creating objects of a specified type. Delphi provides a class factory for the TComObject, TypedComObject, TAutoObject, TActiveXComponent, TActiveVCLControl, and TActiveXControl COM classes.

Class factories take a class identifier (CLSID) as a parameter. Classes without a CLSID and a class factory must be internally instantiated, that is, by calling a constructor. For classes that have a class factory, external clients can create instances of the class by calling global routines such as the Windows API CoCreateInstance or CoGetClassObject, or the Delphi wrapper routines CreateComObject or CreateOleObject. These routines use class factories to instantiate classes.

When creating multiple instances of a class, it may be more useful to directly create a class factory object to instantiate objects of the specified type.

Class factory objects, for all COM objects in a module, are managed in a list held by the com server object for that module. The com server interacts with the ComClassManager, which contains code to manage the class factories. Class factories signal the com server each time an object is instantiated or destroyed so that the server will know when it can be unloaded.

For more information about instantiating a class, see "Instantiating a COM class".


COM class manager

TComClassManager contains code that manages the class factories. The class factory objects are contained in a list owned by the com server object. Each com server has its own list of class factories for its module. However, the ComClassManager is a global variable, declared in the ComObj unit, that is the class manager for all com servers whose modules are linked to the package containing this unit.


COM Servers

TComServer is declared in the ComServ unit and its base class, TComServerObject, is declared in the ComObj unit. The COM server objects manage class factory objects through a ComClassManager object. The com server maintains a list of class factories for all COM objects in the module. Com servers also hold information about loading and unloading the server according to whether any objects on the server are currently in use. Each time a new object is instantiated by a class factory, the com server counter is incremented. When the object is destroyed, the class factory decrements the counter.

See the DEMOs directory for examples using these objects.

For more information about using these classes see the Delphi Library Reference.

Note:

Do not use the Comserv unit in a package. This will cause a compile error. The ComServ contains global registration and management code that can only exist in a main module, that is an EXE or a DLL, not a package.


OLE Automation

TAutoObject supports the IDispatch interface.

More information about using this class is unavailable for field test.


Classes for connectable objects

The TAggregatedObject and its descendent TContainedObject are the base classes for classes the implement of the COM technology of connectable objects. TAggregatedObject is the base class for TConnectionPoints, which implements the IConnectionPointContainer interface. This means that it contains individual objects or connection points that each represent an outgoing interface, IConnectionPoint. Correspondingly, TContainedObject is the base class for TConnectionPoint, which implements the IConnectionPoint interface. TConnectionPoint is instantiated to create the connection point objects that are contained within the TConnectionPoints container object.


About VCL ActiveX objects

The AX unit in the VCL directory includes classes available for testing. Many of the classes in this unit are helper classes for TActiveXControl which is used to create ActiveX controls.

Persistent objects

By implementing the IPersistStreamInit and IPersistStorage interfaces, TActiveXControl supports creating an object that can save its persistent state to a storage or stream.

Viewable objects

By implementing the IViewObject and IViewObject2 interfaces, TActiveXControl t supports

More information about using these classes is unavailable for this field test.

Property pages

Delphi provides classes to create property pages. By implementing the ISpecifyPropertyPages interface, TActiveXControl supports property pages, supplying a list of property page CLSIDs that are displayed in the dialog.

More information about using property pages is unavailable for this field test.


Instantiating a COM class

Accessing an interface requires having a COM object. Once a COM object is instantiated, it is possible to call QueryInterface to obtain an interface pointer. Instantiating the first COM object, however, can be handled in several ways. These are:

Call a constructor for the class.

Call a global routine.

Create a class factory to get the object.

The best choice depends upon the circumstances and/or limitations of the situation.

Classes that do not have a registered CLSID are instantiated using the class constructor.

For class that have a registered CLSID, Delphi provides the global routines CreateComObject and CreateOleObject that are essentially wrappers around the WinAPI CoCreateInstance. These routines create COM and OLE objects, and are documented in the Delphi Reference. Calling a global routine is useful for instantiating a single instance.

For instantiating multiple instances of specific class types that have associated CLSIDs registered, class factories objects can be used. For more information on class factories, see "Class Factories and class instantiation".


Table of OLE/COM terminology

Aggregate object	A compound object composed of several COM objects. One object is designated the control object. See also, Control object.

Binding	Locating an object by its moniker, loading it, and returning a pointer to it.

CLSID	Class identifier registered by an OLE class object in the system registration database. A CLSID is a GUID that refers to a COM class.

Class Factory	A class that implements the IClassFactory interface and from which class objects are instantiated that can create instances of a specific class.

COM	Component Object Model specification describing a binary communication standard based on lifetime management and component interoperability.

Component object	An instance of an object definition, that implements one or more interfaces, and is identified by a CLSID.

Component document	Document containing data in different formats, and created and stored by container applications.

Container application	An application that supports (stores, displays and provides access to) compound documents.

Control object	A designated COM object in an aggregation with a special implementation of IUnknown, called the controlling unknown, that handles all calls the IUnknown methods. The control object determines which interfaces in the aggregation are exposed.

DispID	An interface method identifier used in the IDispatch::Invoke method to call interface methods. See late-binding.

DLL	Dynamic-Link Library.

Early-binding	vtable compile time binding.

GUID	Specific instance of an IIUD used for identification of interfaces and COM classes.

IDL	Interface Definition Language used to create binary, language-independent type libraries.

HRESULT	A result handle returned from a call to an interface method and convertible to a more detailed SCODE.

IID	A GUID that refers to a specific interface. Each interface is associated with an IID. For example, IID_IUnknown is the IID for the IUnknown interface.

In-process server	An object application or server that runs in the same process space as the object's container.

Interface	A semantically-related named group of method prototypes without implementation but with a defined usage.

Late-binding	Run-time binding using the IDispatch interface.

Local server	A server application, running on the same machine as its client, but as an EXE in its own process space.

LRPC	(Lightweight remote procedure call) OLE's RPC protocol that handles interprocess communication between processes on only one machine.

Marshalling	Process of packaging and sending interface parameters across process boundaries for remote procedure calls.

Moniker	Object that implements the IMoniker interface, providing a unique name as a pathway to access and identify a COM object for the purpose of binding.

Object	(in COM) an encapsulation of consistent pre-defined behavior regardless of its location.

ODL	Object Definition Language used to create type libraries independent of any programming language.

OLE Automation	A definition for object manipulation from outside of an application that contains the object. (distinguish from COM objects).

Out parameter	Function parameter assumed to be undefined when the procedure is called. Memory for the object is allocated by the server and freed by the caller.

Proxy	Interface-specific object that runs in the sender's address space and packages parameters in preparation for marshalling. A proxy communicates with the corresponding stub in the address space of the receiver. See Marshalling, Stub.

RPC	Remote procedure call.

SCODE	DWORD value used to pass detailed status code information on the success or failure of an interface method call.

SDI	(Single Document Interface Application) An application that supports only one document at a time.

Stub	Interface-specific object that runs in the receiver's address space and unpackages parameters after marshalling. A stub communicates with the corresponding proxy in the address space of the sender. See Marshalling, Proxy.

Type Library	Used to access the type information for interfaces.

UUID	Universally Unique IDs of which GUIDs are specific instances.

Unmarshalling	Process of unpackaging parameters that were sent across process boundaries after remote procedure calls.

VTBL	An array of method pointers to the corresponding implementations of the methods.The following table lists some commonly used terms in COM/OLE technology:


Introduction

The type library editor is a tool that enables developers to examine and create type information for ActiveX controls and COM objects. This type information can be provided with an object either stand-alone in a type library (.TLB) file, or integrated into the EXE or DLL as a resource.


Type libraries

Type libraries are OLE compound document files that include information about data types, interfaces, member functions  and object classes exposed by an ActiveX control or server. A type library may contain any of the following:

Information about enumeration data types.

Descriptions of one or more objects, such as an interface, IDispatch interface (dispinterface), or COM object class (coclass). Each of these descriptions is commonly referred to as typeinfo.

References to type descriptions from other type libraries.

Including the type library with an application or DLL makes information about the objects it contains available to other applications and programming tools. Libraries can be shipped in any of the following forms:

A resource in a dynamic-link library (DLL). This resource should have the type typelib and an integer ID. It must be declared in the resource (.RC) file as follows:

1 typelib mylib1.tlb

2 typelib mylib2.tlb

There can be multiple type library resources in a DLL. Application developers use the resource compiler to add the .TLB file to their own DLL. A DLL with one or more type library resources typically has the extension .OLB (object library).

A resource in an .EXE file. The file may contain multiple type libraries.

A stand-alone binary file. The .TLB file output by the type library editor is a binary file.


The User interface

The main elements of the type library editor are:

The object list pane

Attributes pages for all elements in the type library

Uses page for the type library

Members pages for interfaces, COM classes, interface members, and enum types

The following sections describe the different elements of the type library editor user interface and what type information is provided.


Object list pane

The object list pane lists all the elements and their type information that are listed in the library. These elements include interfaces, COM classes, interface properties and methods, and enumerations types. The object list pane is an expandable heirachical tree where members of a particular interface appear as children to the interface.

The following color-coded symbols appear to the left of the objects in the object list (left) pane:

Icon	Meaning

A type libary. This can be expanded to expose the individual data types, objects and references to type descriptions in other type libraries.

An interface or dispinterface description.

A COM object class.

A method of the interface.

A property of the interface.

An enumeration type.


Type library information

When a Type library is selected in the Object List Pane, an Attributes page and Uses page become available.

Attributes page

The  attributes page displays type information about the currently selected Library . The following  attributes and flags appear on the Attributes page when a type library

is selected in the main object pane

Attribute	Meaning

Name	The descriptive name of the type library without spaces or punctuation.

GUID	The globally unique 128-bit identifier of the interface.

Version	A particular version of library in cases where multiple versions of the library exist. The Version  is  either a pair of decimal integers separated by a period, or a single decimal integer. The first of the two integers represents the major version number, and the second represents the minor version number of an interface. If a single integer is used, it represents the major version number. Both major and minor version numbers are short unsigned integers in the range between 0 and 65535, inclusive.

LCID	The locale identifier that describes the single national language used for all text strings in the type library and elements.

Help File	The name of the help file associated with the type library

Help String	A short description  of the library. It is strongly recommended that you provide a help string.

Help Context	The Help context ID of the library.

The following flags specify additional aspects about the library.

Flag	Meaning

None	No flags.

Restricted	Prevents the library from being used by a macro programmer.

Control	Indicates that the library represents a control from which a container site will derive additional typelibs or coclasses.

Hidden	Indicates that the library exists but should not be displayed in a user-oriented browser.

Uses page

The uses page in the type library lists type libraries referenced by the type library.

The following columns appear on the Uses page when a type library is selected in the main object pane and list type libraries referenced by the selected library.

Column	Meaning

TypeLibrary	Reference to another type library.

GUID	The globally unique 128-bit identifier of the referenced type library.


Enumeration information

Attributes

The following attributes appear on the attributes page when an enumeration type is selected in the main object pane.

Attribute	Meaning

Name	The name of the enumeration.

GUID	The globally unique 128-bit identifier of the interface.  This is optional.

Help String	A short description of the enumeration. It is strongly recommended that you provide a help string.

Help Context	The Help context ID of the enumeration.

Version	Identifies a particular version of the enumeration in cases where multiple versions of the enumeration exist. The Version  is  either a pair of decimal integers separated by a period, or a single decimal integer. The first of the two integers represents the major version number, and the second represents the minor version number of an interface. If a single integer is used, it represents the major version number. Both major and minor version numbers are short unsigned integers in the range between 0 and 65535, inclusive.

Members

This page contains the type definition of the enumeration type.  This can also include a help string and a help context for each enumeration element.

The following is a sample entry of an enum type for beveling on a panel and includes a help string for each enumeration element. It is strongly recommended that you provide a help string for your enumerations. Applications that use your COM object may depend on a help string.

bvNone = 0;

helpstring = 'bvNone';

bvLowered = 1;

helpstring = 'bvLowered';

bvRaised = 2;

helpstring = 'bvRaised';


Interface and object information

Attributes page

This following attributes and flags appear on the attributes page when an interface or COM object is selected in the main object pane.

Attribute	Meaning

Name	The name of the interface or object.

GUID	The globally unique 128-bit identifier of the interface or object. This attribute distiguishes one interface from other interfaces.

Help String	A brief description of the interface or object.

Help Context	The Help context ID.

Version	Identifies a particular version in cases where multiple versions of the interfaceor object exist. The Version  is  either a pair of decimal integers separated by a period, or a single decimal integer. The first of the two integers represents the major version number, and the second represents the minor version number of an interface. If a single integer is used, it represents the major version number. Both major and minor version numbers are short unsigned integers in the range between 0 and 65535, inclusive.

Parent Interface	The name of the interface that is the base class for this interface. Only applies to interfaces.

Flag	Meaning

DispInterface	The interface describes the methods and properties for an object that must be accessed through IDispatch::Invoke.

Hidden	Indicates that the interface exists but should not be displayed in a user-oriented browser.

Nonextensible	Indicates that the IDispatch implementation includes only the properties and methods listed in the interface description.

Dual	Identifies an interface that exposes properties and methods through IDispatch and directly through the VTBL.

Ole Automation	Indicates that an interface can only use OLE Automation compatible types. Not allowed on dispinterfaces since they are OLE automation compatible by definition.

Members page for an interface

The Members page defines the property and methods of the interface.  A DispID can also be defined for each member. The following sample below defines properties contained in an interface.

property Caption: WideString;

dispid = 1;

property BevelInner: DAXPanelBevel;

dispid = 2;

property BevelOuter: DAXPanelBevel;

dispid = 3;

property BevelWidth: Integer;

dispid = 4;

property BorderWidth: Integer;

dispid = 5;

property WindowBorder: DAXBorderStyle;

dispid = 6;

property Font: Font;

dispid = 7;

The following sample defines methods contained in an interface.

procedure OnClick;

dispid = 1;

procedure OnEnter;

dispid = 2;

procedure OnExit;

dispid = 3;

procedure OnDblClick;

dispid = 4;

procedure OnKeyPress(var Key: Byte);

dispid = 5;

Members page for a COM class

The following columns appear on the Members page when a OLE object is selected and lists the members of the object.

Column	Meaning

Interface	Name of an interface that the COM class will implement.

GUID	Identifies the GUID of the member interface of the object.

Source	Indicates that the member is a source of events.

Default	Indicates that the interface or dispinterface represents the default programmability interface, intended for use by macro languages such as Word Basic, Visual Basic, Delphi, Object PAL and Excel Basic.

Restricted	Prevents the item from being used by a macro programmer such as Word Basic, Visual Basic, Delphi, Object PAL and Excel Basic.

New interfaces can be added to the COM class from a SpeedMenu that appears when you right-click in this window. The SpeedMenu lists the interfaces from which you can choose to add to this class. The list includes interfaces that are defined in the current type library and defined in any type libraries that the current type library references.


Interface members

Attributes page

The following attributes and flags appear on the attributes page when a property or method of an interface is selected in the main object pane.

Attribute	Meaning

Declaration	The declaration of the property or method.

ID	Dispatch ID

Help String	Identifies the documentation string of the property or method. This usually contains a descriptive title of item.

Help Context	Identifies the Help context ID of the property or method.

Flag	Meaning

Restricted	Prevents the property or method from being used by a macro programmer.

Source	 Indicates that the member returns an object or VARIANT that is a source of events.

Bindable	Indicates that the property supports data binding.

Request Edit	Indicates that the property supports the OnRequestEdit notification.

Display Bindable	Indicates a property that should be displayed to the user as bindable.

Default Bindable	Indicates the single, bindable property that best represents the object.

Hidden	Indicates that the property  exists but should not be displayed in a user-oriented browser.


Valid types

Only the following Pascal types are allowed in OLE automation (dispatch) type library definitions:

Pascal type	OLE varient type	description

Smallint	VT_I2	2-byte signed integer

Integer 	VT_I4 	4-byte signed integer

Single	VT_R4	4-byte real

Double     	VT_R8	8-byte real

Currency	VT_CY	currency

TDateTime	VT_DATE	date

WideString      	VT_BSTR	binary string

WordBool        	VT_BOOL	True = -1, False = 0

Variant    	VT_VARIANT	Variant FAR*

Byte        	VT_UI1	unsigned short

Besides these pascal types, any interfaces and types defined in the library or defined in referenced libraries can be used in a type library definition.

The following types can also be used in non-dispatch interface type libraries.

Pascal type	OLE varient type	description

Pointer	VT_PTR	pointer type

HResult	VT_HRESULT

Word	VT_UI2	unsigned short

Comp	VT_I8	signed 64-bit integer

PChar	VT_LPSTR	null-terminated string

PWideChar	VT_LPWSTR	wide null-terminated string

Shortint	VT_I1	signed char


Creating a new type library

To create a new type library,

1.	Choose File|New to open the New Items dialog box.

2.	Choose the ActiveX page which opens the New page.

3.	Select the Type Library Icon.

4.	Choose OK. The type library editor opens with a prompt to enter a name for the type library.

5.	Enter a name for the type library and press Enter.

Interfaces, CoClass'es, enum's, properties and methods can be added to the type library.

Adding an interface to the type library

To add an interface,

1.	Right-click in the object list pane.

2.	From the SpeedMenu choose Add, then choose Interface from the next speed menu. An interface is added to the object list pane prompting you to add a name.

3.	Type a name for the interface in the interface and press Enter.

The new interface contains defaults attributes which can be modified as needed. The members page is empty and you can now add properties and methods to suit the purpose of the interface.

Adding interface members to the type library

To add interface members to an interface,

1.	Right-click in the object list pane.

2.	From the SpeedMenu choose Add, then choose Property or Method from the next speed menu. An interface member is added to the object list pane prompting you to add a name.

3.	Type a name for the member and press Enter.

The new member contains default attributes in its attributes page which can be modified to suit the member.

Properties and methods can also be added my typing directly into the members page. For example you could type the following property declarations into the members page of an interface:

property AutoSelect: WordBool;

dispid = 1;

property AutoSize: WordBool;

dispid = 2;

property BorderStyle: BorderStyle;

dispid = 3;

After the members have been added to an interface member page, the members appears as separate items in the object list pane each with its own attributes page where you can modify each new member's attributes.

Adding a COM class to the type library

To add a COM class to a type library,

1.	Right-click in the object list pane.

2.	From the SpeedMenu choose Add, then choose CoClass from the next SpeedMenu. ACOM class is added to the object list pane prompting you to add a name.

3.	Type a name for the class and press Enter. The new class contains default attributes in its attributes page which can be modified to suit the class. The members page is blank. To add members,

4.	Right-Click in the members page for the class. A SpeedMenu appears which contains a list of interfaces from which you can choose. The list includes interfaces that are defined in the current type library and defined in any type libraries that the current type library references.

5.	Double-click on an interface you want the class to implement.

 The interface is added to the page with along with its GUID and other attributes.

Adding an enum to the type library

To add enums to a type library,

1.	Right-click in the object list pane.

2.	From the SpeedMenu choose Add, then choose Enum from the next speed menu. An Enum type is added to the object list pane prompting you to add a name.

3.	Type a name for the member and press Enter.

 The new enum is empty and contains default attributes in its attributes page which can be modified to suit the enum and an empty members page for the enum will be blank.

Add values to the enum by typing directly into the members page. For example you could type the following declarations into the members page of an enum used by a button control:

mbLeft = 0;

helpstring = 'mbLeft';

mbRight = 1;

helpstring = 'mbRight';

mbMiddle = 2;

helpstring = 'mbMiddle';


Web delivered applications

An ActiveForm is a Delphi Form that is also an ActiveX control and can therefore be displayed and run from within Web browsers such as Microsoft Internet Explorer and Netscape Navigator. An HTML page is created to contain the reference to the ActiveX control and specify its location within the page. Inside the Web browser the form behaves just like a stand-alone Delphi Form. The Form may contain any VCL or ActiveX components, including custom built VCL controls.

Delphi provides an ActiveForm expert to facilitate the creation of ActiveForms. The expert will create

an ActiveX library project to contain the ActiveForm,

an active form which you can customize.

After you have designed and compiled the ActiveForm project into an ActiveX library, you can deploy the project to your Web server and Delphi will create a test HTML page with a reference to the ActiveForm.


Creating an ActiveForm

To create the ActiveForm,

1.	Choose File|New to open the New Items dialog box.

2.	Choose the ActiveX page which opens the New page with the default new ActiveForm highlighted.

3.	Choose OK.

If the current project is not an ActiveX library, a warning message will ask if you wish to create a new ActiveX library project. An active form is contained in an ActiveX Library; therefore, an ActiveX library project must first be created to contain the active form. Choose OK.

Delphi adds a blank form to your ActiveX library project and creates the ActiveX library project if needed. At this point you can add controls and design the form as you wish. For information on building forms, see the User's Guide, "Chapter 2, Building Forms".


Deploying the ActiveForm

Once you have finished designing the ActiveForm, you can deploy it to your Web server. In order to deploy the ActiveForm project, Web deployment compiler options must be set appropriately, and the project must be compiled.

1.	Select Project|Options to open the Project Options dialog box.

2.	Choose the Web Deployment page which appears as follows:

3.	Set the Target Dir to the location as a path of the ActiveForm .DLL on the Web server. This can be a standard path name or a UNC path.

4.	Set the Target URL to the location as a Uniform Resource Locators (URL) of the ActiveForm DLL on the Web Server.

5.	Set the HTML Dir to the location as a path of the HTML file that contains a reference to the ActiveForm. This can be a standard path name or a UNC path.

6.	Choose OK.

7.	Choose Project|Build All. This creates the ActiveX library (.DLL) that contains the ActiveForm.

8.	Choose Project|Web Deploy. The ActiveX library (.DLL) is placed in the Target Dir of step 3. The HTML file is the same name as the project file but with a .HTM extension, and it is created in the HTML Dir of step 5. The HTML file contains a URL reference to the DLL at the location specified in step 4.

9.	Invoke your Web browser and view the created HTML page.

When this HTML page is viewed in the Web browser, your form is displayed and runs as an embedded application within the browser.


Introduction

This section describes the Internet components that allow you to create an application that can produce HTML content directly from information databases and files, and interact with other Internet applications. The creation of several types of Internet applications will be discussed.

Delphi allows you to create Web server applications as Dynamic Link Libraries (DLL) that may contain any non-visual component. Internet-specific components make it easy to create event handlers that are associated with a specific Uniform Resource Identifier (URI) and, when processing is complete, to programmatically construct HTML documents and transfer them to the client.

Frequently, the basis of this content is drawn from databases. Delphi Internet components can be used to automatically manage connections to databases, allowing a single DLL to handle numerous simultaneous, thread-safe database connections.


Terminology and Standards

Many of the protocols that control activity on the Internet are defined in Request for Comment (RFC) documents that are created, updated and maintained by the Internet Engineering Task Force (IETF), the protocol engineering and development arm of the Internet. There are several important RFCs that you will find useful when writing Internet applications:

RFC822 "Standard for the format of ARPA Internet text messages" describes the structure and content of message headers

RFC1521 "MIME (Multipurpose Internet Mail Extensions) Part One: Mechanisms for Specifying and Describing the Format of Internet Message Bodies" describes the method used to encapsulate and transport multi-part and multi-format messages.

RFC1945 "Hypertext Transfer Protocol -- HTTP/1.0"describes a transportation mechanism used to distribute collaborative hypermedia documents.


Finding Internet standards documents

The IETF maintains a library of the RFCs on their web site, www.ietf.cnri.reston.va.us.


URI vs. URL

The more familiar Uniform Resource Locator (URL) is a sub-set of the Uniform Resource Identifier (URI) defined in the HTTP standard, RFC1945. Web server applications frequently produce content from many sources and the final result is not location specific, but is created as necessary.


An overview of HTTP server activity

The client/server nature of web browsers is deceptively simple. To most users, retrieving information on the World Wide Web is a simple procedure; click on a link, and the information appears on the screen. More knowledgeable users have some understanding of the nature of HTML syntax and the client/server nature of the protocols (FTP, HTTP, etc.) used. This is usually sufficient for the production of simple, page oriented Web site content. Web page authors in a more complex environment are confronted with a wide variety of options to automate the collection and presentation of data with HTML.

Before we examine the Delphi components that help you collect and present HTML documents, let's examine how the client issues a request and how the server deals with those requests in more detail, specifically:

How a client composes a request

What a Web server does when it receives a request

How page-creation programs receive information from the Web server

How the results are passed back to the Web server


Composing client requests

When an HTML hypertext link is selected (or the user otherwise specifies a URL) the browser collects information about the protocol, the specified domain, the path to the information, the date and time, the operating environment, the browser itself, "cookies," other content information and composes a request.

For example, if we wish to display a page of images based on criteria selected by clicking buttons on a form, the client might construct this URL:

http://www.TSite.com/art/gallery.dll/animals?animal=dog&color=black

which specifies an HTTP server in the www.TSite.com domain. The client contacts www.TSite.com, connects to the HTTP server, and passes it a request. The request might look something like this:

GET /art/gallery.dll/animals?animal=dog&color=black HTTP/1.0

Connection: Keep-Alive

User-Agent: Mozilla/3.0b4Gold (WinNT; I)

Host: www.TSite.com:1024

Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*


Serving client requests

The server receives this request and may perform any number of actions, based on the server's configuration. If the server is configured to recognize the /gallery.dll portion of the request as a program, the way information about the request is passed to the program depends on the type of program:

If the program is a Common Gateway Interface (CGI) program, the server passes the information contained in the request directly to the CGI program. The server waits while the program executes. When the CGI program exits, it passes the content directly back to the server.

If the program is a Win-CGI, the server opens a file, writers out the request information and executes the Win-CGI program. The server also passes the location of the file containing the client information and the location of a file that the Win-CGI program should use to create content. The server waits while the program executes. When the program exits, the server reads the data from the content file written by the Win-CGI program.

If the program is a Dynamic Link Library (DLL), the server loads the DLL (if necessary) and passes the information contained in the request to the DLL as a structure. The server waits while the program executes. When the DLL exits, it passes the content directly back to the server.

In all cases, the program acts on the request and performs actions specified by the programmer: accessing databases, simple table lookups, calculations, constructing or selecting HTML documents, etc.


Responding to client requests

When each program finishes, it constructs a page of HTML code and passes it back (via the server) to the client for display. This procedure also differs based on the type of program:

When the CGI script finishes it constructs a page of HTML, writes it to a file, writes any response information to another file, and passes the locations of both files back to the server. The server opens both files and passes the HTML back to the client.

When the DLL finishes, it passes the HTML and any response information directly back to the server, which passes it back to the client.

Creating a Web server application as a DLL reduces system load and resource use by reducing the number of processes and disk access necessary to service an individual request.


Delphi web server applications

Web server applications extend the functionality and capability of existing Web servers. Any operation that you can perform with a Delphi application can be incorporated into a Web server application.

A Web Module containing a THTTPDispatcher component is the basis of Delphi Web server applications. The dispatcher triggers an action's event handler based on the additional path information of a request. Code associated with these event handlers has access to all of the request information as properties of a descendant of THTTPRequest. Specialized HTML producer components (like THTMLPageProducer and THTMLQueryTableProducer) allow you to dynamically produce custom HTML code. When your program finishes, it passes the result back to the client (via the server) by using a descendant of THTTPResponse.


Types of Web server applications

You can use Delphi to create four types of Web server applications. Each type uses a type-specific descendant of HTTPApplication, HTTPRequest, and HTTPResponse:

Microsoft Server  (ISAPI) DLL	TISAPIApplication	TISAPIRequest	TISAPIResponse
Netscape Server  (NSAPI) DLL	TISAPIApplication	TISAPIRequest	TISAPIResponse
Console CGI application	TCGIApplication	TCGIRequest	TCGIResponse
Windows CGI application	TCGIApplication	TWinCGIRequest	TWinCGIResponse
ISAPI and NSAPI

An ISAPI or NSAPI Web server application is a DLL that is loaded by the Web server client request information is passed to the DLL as a structure and evaluated by TISAPIApplication, which creates the dispatcher, TISAPIRequest and TISAPIRequest objects.

CGI stand-alone

A CGI stand-alone Web server application is a console application that receives client request information on standard input and passes the results back to the server on standard output. This data is evaluated by TCGIApplication, which creates the dispatcher, TCGIRequest and TCGIRequest objects.

Win-CGI stand-alone

A Win-CGI stand-alone Web server application is a Windows application that receives client request information from a configuration settings (INI) file written by the server and writes the results to a file that the server passes back to the client. The INI file is evaluated by TCGIApplication, which creates the dispatcher, TWinCGIRequest and TWinCGIRequest objects.


Internet object overview

The Internet components are organized into several functional areas, listed below.

Components

These non-visible components can be found on the Internet page of the component palette and can be added to a Web module:

WebSession

HTTPDispatcher

HTMLPageProducer, HTMLQueryTableProducer, HTMLDataSetTableProducer

ServerSocket, ClientSocket

Primary objects

These objects have descendants that are specific to each type of Web server application and are integral to the construction of Delphi Web server applications:

THTTPApplication (TISAPIApplication, TCGIApplicaion)

THTTPRequest (TISAPIRequest, TCGIRequest, TWinCGIRequest)

THTTPResponse (TISAPIResponse, TCGIResponse, TWinCGIResponse)

Common ancestor objects

These objects can be used to create new descendant objects:

TCustomContentProducer

TCustomHTTPDispatcher

THTMLDSTableProducer

HTML table format control objects

These objects can be used to control the formatting of HTML tables:

THTMLTableAttributes

THTMLTableElementAttributes

THTMLTableHeaderAttributes

THTMLTableRowAttributes, THTMLTableCellAttributes

Database table format control objects

These objects can be used to control the formatting of information from database tables:

THTMLTableColumn

THTMLTableColumns

Borland custom tag objects

This object is used to determine the attributes of custom HTML tags:

THTMLTagAttributes

Socket objects

These objects can be used to create applications that use sockets:

TServerAcceptThread

TServerClientThread

TServerClientWinSocket

TClientWinSocket

TWinSocketStream

 Common socket ancestor objects

TCustomSocket

TCustomServerSocket

TCustomWinSocket

Sync objects

These objects are synchronization objects for multi-threaded applications:

TSynchroObject

THandleObject

TEvent

TSimpleEvent

TCriticalSelection

Additional objects

TMask

THTTPDataLink

THTTPActionItems


Creating Web Server Applications

All new Delphi Web server applications are created by selecting File | New from the menu of the Delphi main window to display the New Items dialog and selecting the Web Server Application. A dialog box will allow you to select one of the Web server application types:

ISAPI and NSAPI	Selecting this type of application adds the library header of the project files and required entries to the uses and exports clause of the project file.
CGI stand-alone	Selecting this type of application adds the required entries to the uses clause of the project file and selects the Generate console application project option.
Win-CGI stand-alone	Selecting this type of application adds the required entries to the uses clause of the project file.
This creates a new project configured to use Internet components and containing an empty data module. You should add an HTTPDispatcher component to the module.

Any non-visual component (such as DataSource, Table and Query components) may be added to the module, and additional data modules can be added to the project. However, only one dispatcher can be added to any module and only the dispatcher associated with the first module created during execution will receive client request information from the Web server. If a second module containing a dispatcher is created during execution, the Web server application will generate a run-time error.


Using Web modules

A Web module is a TDataModule and may be used in the same way; to provide centralized control for nonvisual components in a application.

Warning:	Web modules in a DLL based Web server application are cached for later re-use to increase response time. The state of the dispatcher and the action list is not re-initialized between requests. Changing the Enabled or Default property of an action during execution may cause unexpected results if that module is used by subsequent client requests.


Using the WebSession component

The TWebSession component is a descendant of the TSession component and may be used in the same way; to provide global control for all database connections in a application.

When the WebSession component is added to a Web module or data module, it specifies a default SessionName property. Adding a TWebSession component to a Web module or data module causes the SessionName property of any component on that module to be changed to the SessionName associated with the TWebSession component. Similarly, when a component with a SessionName property is added to a Web module or data module with a TWebSession component, the SessionName property will be changed to the SessionName associated with the TWebSession component.


Using the THTTPDispatcher component

When a client request is passed to the Web server application, the dispatcher compares the contents of the PathInfo property of the associated descendant of THTTPRequest to the URI property of each action associated with the dispatcher, in order. If a match is found, the OnAction event handler associated with that action is triggered. If no match is found, the event handler associated with the default action, if any, is triggered. Control is passed back to the dispatcher when the event handler finishes.

If the Handled parameter is set to True by the event handler (the default) the response is passed back to the server. If the dispatcher reaches the end of the action list (including the default action, if any) and no actions have been triggered, nothing will be passed back to the server. The server will simply drop the connection to the client.

More detail on the selection process can be found See "Controlling program flow using dispatcher actions".


Adding actions to the dispatcher

You can open the action editor by double-clicking the dispatcher, or right-clicking the component and selecting Action Editor from the menu. Actions can be added to the action editor by right-clicking the action editor and selecting Add Action from the menu.


Working with Actions

When an action is selected in the action editor, its properties can be modified in the Object Inspector.


Specifying a URI

The URI property of an action is used by the dispatcher to determine which action's event handler is triggered. The value of this property should be the path information portion of the URI. For example, given this URL:

http://www.TSite.com/art/gallery.dll/mammals?animal=dog&color=black

and assuming that the /gallery.dll part is the Web server application, the path information portion is:

/mammals


Controlling program flow using dispatcher actions

When the dispatcher receives the client request, it triggers the first action in the action list. If the Enabled property of the action is False the action exits, returning False. If the Enabled property is True, the action compares the contents of:

the PathInfo property of the associated descendant of THTTPRequest to
the URI property of the action and

the HTTPMethod property of the associated descendant of THTTPRequest to
the MethodTag property of the action

If the values of each pair of properties do not match, the action exits, returning False. If they match, the OnAction event handler associated with that action is triggered. The OnAction event handler sets the value of the Handled parameter to True and executes any program code. When the event handler exits, the action exits and returns the value of the Handled parameter to the dispatcher.

At this juncture, if the Default property of the action is True, the dispatcher stores the name of that action as the default. The dispatcher then examines the value returned by the action. If it is True, the dispatcher exits and the application determines the status of the content, if any. If the action returns False, the dispatcher proceeds to the next action.

Note:	An action may be selected by the dispatcher as the default action regardless of the state of the action's Enabled property.

The dispatcher continues through the list until either:

an action returns True when it exits

there are no additional actions to consider

If there are no additional actions to consider and the dispatcher has selected a default action, that action is triggered. The state of the Enabled property is not examined. If the Default property of that action is False when the action is triggered, the action exits. If the Default property is True, the OnAction event handler associated with that action is triggered. As before, the OnAction event handler sets the value of the Handled parameter to True and executes any program code. When the event handler exits, the action exits and returns the value of the Handled parameter to the dispatcher.

Regardless of the value the action returns, the dispatcher exits. Control is passed back to the application. The application then determines the status of the content, if any.

Controlling action selection with the Enabled property

The state of the Enabled property can be changed during execution to determine which actions evaluate PathInfo and HTTPMethod.

Caution:

Changing the Enabled property of an action during execution may cause unexpected results for subsequent requests. If the Web server application is a DLL, modules are cached and their initial state is not re-initialized.

Controlling action selection with the Default property

By setting the default property of an action to True, you can ensure that this action will be executed for any URI if the dispatcher reaches the end of the action list. When an the Default property of an action is set to true, the default property of all other actions contained in the same dispatcher is set to False.

Caution:

Changing the Default property of an action during execution may cause unexpected results for the current request. If the Default property of an action that has already been triggered is set to True, that action will not be re-evaluated and the dispatcher will not trigger that action when it reaches the end of the action list.

Caution:

Changing the Default property of an action during execution may cause unexpected results for subsequent requests. If the Web server application is a DLL, modules are cached and their initial state is not re-initialized.


Using THTTPRequest to access client request information

The parameters of the client request are loaded into the properties of the descendant of THTTPRequest object. All of these properties are read-only. Refer to the object documentation for a complete list of these properties.


Using properties that contain request header information

These properties all contain the values of the headers that were passed to the client.

Accept	ContentType	From	RemoteAddr
CacheControl	ContentVersion	Host	RemoteHost
Connection	Cookie	HTTPMethod	ServerPort
Content	CookieFields	IfModifiedSince	Title
ContentEncoding	Date	Method	URL
ContentFields	DerivedFrom	PathTranslated	UserAgent
ContentLength	Expires	Referer


Producing HTML

Delphi HTML producer components perform specialized functions that allow you to perform database operations that return formatted HTML tables or scan HTML documents for special tags that are replaced with customized HTML code.


Using THTMLPageProducer

The THTMLPageProducer component accepts HTML as a string list or from a file. If it encounters the HTML-transparent tag (defined below), it triggers the OnHTMLTag event handler. The event handler can determine the type of tag encountered and replace it with customized content.

THTMLPageProducer tag syntax

The syntax of these tags is:

<#TagName Param1=Value1 Param2=Value2 ...>

The less-than ('<') and greater-than ('>') symbols define the entire scope of the tag. The octothorpe ('#') must immediately follow the less-than symbol and identifies this as a THTMLPageProducer tag.

Note:	This differs from HTML code; there is no <\> construct to terminate this statement. In addition, tags in this format will be ignored if the text of a tag is inadvertently passed to an HTML client.

The TagName can be any valid Object Pascal identifier and serves to identify the type of tag. Currently there are several pre-defined tags that are associated with the Tag property of the THTMLPageProducer component:

LINK	(tgLink)
IMAGE	(tgImage)
TABLE	(tgTable)
IMAGEMAP	(tgImageMap)
Any other tag name will be associated with tgCustom.

The tag name can be followed by any number of parameter and value pairs. Each pair must be delimited by white space and the equals symbol ('=') must not be separated from the parameter or the value by any other symbol.

A typical use of this function would use the HTMLFile property of the THTMLPageProducer component to specify a file containing HTML code and call the Content method to evaluate the HTML:

procedure TWebModule1.HTTPDispatcher1Actions0Action(Sender: TObject;

  Request: THTTPRequest; Response: THTTPResponse; var Handled: Boolean);

begin

  PageProducer1.HTMLFile := 'Greeting.html';

  Response.Content := PageProducer1.Content;

end;

Using a file that contained this HTML code:

<HTML>

<HEAD><TITLE>Our brand new web site</TITLE></HEAD>

<BODY>

Hello <#UserName>!  Welcome to our web site.

</BODY>

</HTML>

an application can replace the custom tag (<#UserName>) in the HTML during execution. When the THTMLPageProducer component encounters the tag, it triggers the OnHTMLTag event, where the UserName part can be replaced:

procedure TWebModule1.HTMLPageProducer2HTMLTag(Sender: TObject;

  Tag: TTag;const TagString: String; TagParams: TStrings;

  var ReplaceText: String);

begin

  if CompareText(TagString,'UserName') = 0 then

    ReplaceText := 'Dr. Bob';

end;

The value of Response.Content would be:

<HTML>

<HEAD><TITLE>Our brand new web site</TITLE></HEAD>

<BODY>

Hello Dr. Bob!  Welcome to our web site.

</BODY>

</HTML>

If the THTMLPageProducer component encounters a tag with no associated event handler, the tag is replaced with an empty string.


Using THTMLQueryTableProducer

When an URI query is passed to a THTMLQueryTableProducer, it performs the query on the associated database and formats the result as an HTML table. Properties of the component can be used to select style elements of the table such as color, border and separator thickness, etc.


Using THTMLDSTableProducer

Similar to THTMLQueryTableProducer, this component formats a dataset as an HTML table. Properties of the component can be used to select style elements of the table such as color, border and separator thickness, etc.


Using THTTPResponse to pass information back to the client

The final action of an event handler triggered by the dispatcher should be to assign the generated content to the Content property of the descendant of THTTPResponse object. The Web server application passes the document and any other state information to the server and exits.


Using properties that contain request header information

These properties and their values will be returned to the client as header and content pairs.

Allow	Date	Location	SetCookie
ContentEncoding	DerivedFrom	LogMessage	StatusCode
ContentLength	Expires	Realm	Title
ContentStream	HTTPRequest	ReasonString	Version
ContentType	LastModified	Server	WWWAuthenticate
ContentVersion


Using the Content property

The Content property of the descendant of THTTPResponse object can be a string of HTML commands, graphic content as a bit-stream, or any other MIME content type.


Using the ContentStream property

The ContentStream property of the descendant of THTTPResponse object can be a string of HTML commands, and HTML file, graphic content as a bit-stream, or any other MIME content type.

Note:

If the value of the ContentStream property is not null, the Content property is ignored.


Introduction

More information is provided on the following features:

IDE Enhancements

VCL Enhancements

RTL Enhancements

Graphic Enhancements

Multibyte Character Support

COM/ActiveX Support

Database Development

Borland DataBase Engine and SQL Links Features

DataBase Explorer Enhancements

Internet Development Features

Compiler changes

Boolean Data Types

Wide String types

Exit procedures

Out parameters

Safecall calling convention


IDE Enhancements

DLL Debugging

You can now specify an .EXE file to run for the purpose of debugging a DLL project. Choose Run|Parameters and enter the name of the .EXE file in the "Host application" edit box.

Code Editor Enhancements

Visual enhancements for debugger support have been added. The Code editor window now displays a gutter and gutter glyphs, which can be configured or turned off in the Environment Options dialog (select Tools|Options|Display). An "Evaluation hints" debugger option displays the value of any variable, property, etc. when you pass the mouse over it in the editor window.

IME (input method editor) support has been added for Asian-language environments. Comments and string values can now contain multibyte characters. Object Pascal identifiers (names of variables, constants, etc.) cannot use multibyte characters.

Pop-up code templates are available for common Pascal syntactic structures such as "if ... then ..." and "while" statements and "try ... except" blocks. To access code templates in the editor, press control-j. To add or edit templates, select Tools | Code Templates. Code templates are not multibyte-enabled.

Unit Output Directory

The Directories/Conditionals page of the Project Options dialog has a new edit box called Unit Output Directory. Use this to specify the directory where compiled unit files are stored. Project .DLL and .EXE files are still sent to the regular Output Directory.

Shared Repository Directory

The Preferences page (under Tools | Options) now has an edit box for specifying the Shared Repository Directory. This location is stored in the Windows registry. In Delphi 2.0 it had to be be changed manually in the Windows Registry Editor (by creating a String Value called "BaseDir" in the "HKEY_CURRENT_USER|Software|Borland|Delphi|2.0|Repository" key). For more information on the repository, check Help.

Component Templates

Component templates are groups of components that you add to a form in a single operation. Templates allow you to configure components on one form, then save their arrangement and default properties on the Component palette for reuse on other forms. See the online Help included with this field test for details.


VCL Enhancements

TMenuItem

If the RadioItem and Checked properties are both set to True, a bullet (rather than a check mark) will appear next to the menu item.

TSplitter

A new visual component, TSplitter, has been added to the Additional page of the Component palette. Splitters allow you to divide a form into arbitrary rectangular regions that can be resized by the user at run time.

Input Method Editor (IME) Support

Visual components that support entry or editing of text now have published properties called ImeMode and ImeName. These properties allow you to create IME-enabled controls for Asian-language versions of Windows. ImeName, which points to an input method editor, lets you to select an IME specifically for the component. ImeMode controls the behavior of the IME.

The ImeName and ImeMode properties have been added to TForm itself; to TEdit, TMemo, TListBox, TComboBox, TRichEdit, and TMaskEdit; to the corresponding database controls TDBEdit, TDBMemo, TDBListBox, TDBComboBox, and TDBRichEdit, along with TDBGrid, TDBLookUpListBox, and TDBLookupComboBox; and to the Win 3.1 controls TFileListBox, TDirectoryListBox, TDBLookupList, and TDBLookupCombo.

Asian-language developers should also see the note below on the TFont.Charset property.


Graphics Enhancements

The following changes and enhancements have been made to Delphi's graphics support:

A new visual component, TAnimate, encapsulates the ANIMATE control. TAnimate's FileName property points to an AVI file; if Active is set to True, the AVI is displayed on the form within the TAnimate component. NOTE: TAnimate does not work with AVI files that have sound.

The InitGraphics routine is no longer required and has been deleted from graphics.pas.

EOutOfResources and EInvalidOperation exception classes have beeen moved from controls.pas to classes.pas.

TFont has a new published property:

property Charset: TFontCharset; default DEFAULT_CHARSET;

Charset is an integer that corresponds to the character set selected for the text object where the TFont instance occurs. For Asian characters to display properly, Charset must point to a character set that contains those characters. The easiest way to set Charset is to open the Object Inspector's Font dialog for the text object (e.g. TLabel) and select a font from the list box.

Thread-safe support has been added for multithreaded operations to graphics classes.

TCanvas has two new methods:

procedure Lock;

procedure Unlock;

The Lock method grabs exclusive access to a drawing canvas, and Unlock releases it. Background threads should always lock a canvas before drawing to it. The VCL will be updated to lock the canvas implicitly during normal VCL painting operations (Paint method and OnPaint events).

Bitmap objects have implicit thread locks only in the Assign operation. If you need to have multiple threads writing to the same bitmap object, you should lock the bitmap's canvas to synchronize access to the bitmap.

TBitmap has been enhanced to use DIB sections instead of memory streams. This reduces system memory use by 50% and preserves the pixel format of the original file even through bitmap modifications.

Support has been added for 15bpp, 16bpp, and 32bpp BMP pixel formats, and for optional color palettes in high-color BMP files. Palettes assigned to high-color bitmaps will be stored in the BMP file. Dithering and halftone palettes are supported when the destination bpp is less than the source bpp.

Palette realization has been removed from bitmap loading code. This eliminates screen flicker when loading forms with 256-color bitmaps, and improves bitmap and form load times by a factor of ten or more in large applications.

New properties have been added to TBitMap:

property DIBMemory: Pointer;

This returns a pointer to the image pixel data in memory for direct modification of the image. This propertay is  read-only.

TBitmapHandleType = (bmDIB, bmDDB);

property HandleType: TBitmapHandleType;

Changing HandleType converts an internal bitmap handle from DIB to device format, or vice-versa. Some color formatting may be lost in the conversion.

TGraphic has two new properties. Graphic objects besides TBitmap can now indicate palette and transparency:

property Transparent: Boolean;

property Palette: HPALETTE;

When Transparent is True, the image does not completely cover its rectangular area. The read-only property Palette is a handle to the color palette of the image. If the graphic doesn't use palettes, Palette is 0.

TImage no longer assumes TBitmap's palette and transparency settings. Instead it uses TGraphic's generic Palette and Transparent properties. TImage.PictureChanged now initiates a palette realization, if necessary.


RTL Enhancements

Math Unit

New integer functions have been added to the Math unit:

function SumInt(const Data: array of Integer): Integer register;

function MinIntValue(const Data: array of Integer): Integer;

function MaxIntValue(const Data: array of Integer): Integer;

These functions behave like the corresponding floating-point functions Sum, MinValue, and MaxValue.

SystemUnit

See the section on Compiler Changes, below.

SysUtils Unit

The SysUtils unit contains new functions and procedures:

function CompareMem(P1, P2: Pointer; Length: Integer):

  Boolean; assembler;

CompareMem compares Length bytes of memory referenced by P1 to Length bytes of memory referenced by P2, and returns True if the memory segments are the same.

procedure RaiseLastWin32Error;

procedure Win32Check(RetVal: BOOL);

RaiseLastWin32Error retrieves the code for the last occurring Win32 error and raises an exception with the error code and associated message. Win32Check is used to verify the value of any Win32 API function that returns a BOOL to indicate success; if the function returns False (indicating failure), Win32Check calls RaiseLastWin32Error to raise an exception.


Multibyte Character Support

Several functions have been added or modified in the RTL to support multibyte characters. AnsiLowerCase, AnsiUpperCase, ChangeFileText, ExtractFileDir, ExtractFileExt, ExtractFileName, and ExtractFilePath have been modified. The following functions are new:

AnsiCompareFileName

AnsiExtractQuotedStr

AnsiLastChar

AnsiLowerCaseFileName

AnsiPos

AnsiQuotedStr

AnsiStrComp

AnsiStrIComp

AnsiStrLastChar

AnsiStrLComp

AnsiStrLIComp

AnsiStrLower

AnsiStrPos

AnsiStrRScan

AnsiStrScan

AnsiStrUpper

AnsiUpperCaseFileName

ByteToCharIndex

ByteToCharLen

ByteType

CharToByteIndex

CharToByteLen

IsDelimiter

IsPathDelimiter

LastDelimiter

StrByteType

Finally, the LeadBytes type has been added:

LeadBytes: set of Char = [];


COM/ActiveX Support

TOleContainer

A new published property has been added to TOleContainer:

   property AllowActiveDoc: Boolean; default True;

If this property is set to True you can insert ActiveDocs (DocObjects) into the OleContainer, and the OleContainer can respond when an embedded object requests an ActiveDoc interface. Because ActiveDoc-enabled components have a slightly different border, you may sometimes want to set this property to False to maintain the consistent appearance of a form.

ActiveX Classes and Demo Applications

This field test includes ActiveX demos, an ActiveX Class Framework, and new tools to facilitate building ActiveX  controls. To find the new tools, choose File | New and click the ActiveX tab. See the online Help for more information.

The ActiveX demos were created with the Class Framework, which lets you build ActiveX controls from VCL controls. The Framework supports implementing OLE automation servers as well as ActiveX controls that can be embedded in a variety of containers, including:

Internet Explorer 3.0

Delphi 2.0

Borland C++

Visual C++

Visual Basic

Paradox 8.0

IntraBuilder 1.0

ActiveX Control Pad

The classes in the Framework implement COM objects, including ActiveX controls, non-visual OLE automation servers, and ActiveX property pages. In the case of ActiveX controls, work is divided between two separate Object Pascal class types: the Windows implementation and the OLE controller object. The developer implements a standard VCL control descended from TWinControl (e.g. TPanel) and an OLE controller object derived from TActiveXControl. The OLE controller object keeps a pointer to the the Windows implementation object, which it creates, manipulates, and destroys in response to run-time events. The Windows implementation object forwards events from the UI to the controller object, which may in turn forward those events to its site.

The ActiveX class hierarchy includes the following objects:

       TComObject

 TTypedComObject

   TAutoObject

     TActiveXControl

               [TMyActiveXControl]

       TForm

         TPropertyPage

           [TMyPropertyPage]

An ActiveX control is a class (e.g. "TMyActiveXControl") descended from TActiveXControl. An ActiveX property page is a class (e.g. "TMyPropertyPage") descended from TPropertyPage.

Two demos are included with this field test release. DAXSrv (in Delphi 3.0\Demos\ActiveX\Daxsrv) is a panel ActiveX control built from the VCL TPanel control. It includes a property page that can be used to modify properties. (For example, you can add the control to a Visual Basic form, then modify its properties.) TestSrv (in Delphi 3.0\Demos\ActiveX\Testsrv) is a non-visual OLE automation server derived from TAutoObject. To build either of these demos, run the batch file call Build.bat in the appropriate directory.


Database Development

Changes to DataSet Objects

A new public property has been added to TDataSet:

       property CacheBlobs: Boolean; default True;

Setting this to True turns on blob caching, which improves performance when scrolling through records with on-screen blob fields (for example, in a DBCtrlGrid). Additional memory is used to store the cache.

A new method has been added to TMemoryDataSet:

       procedure CreateDataSet;

CreateDataSet works like TTable.CreateTable.

In preparation for extending the functionality of datasets, BDE-specific dependencies have been removed from TDataSet and put into a new object, TBDEDataSet. Some additional functionality is now found in TDBDataSet instead of TDataSet. TDataSet itself contains three new functions:

  function IsEmpty: Boolean;

    {Checks for a dataset without records}

  function CompareBookmarks(Bookmark1, Bookmark2: TBookmark):

    Integer;

    {Compares two bookmarks and returns 0 if they are equal}

  function BookmarkValid(Bookmark: TBookmark): Boolean;

    {Verifies that a bookmark is valid}

All dataset code in DB.PAS that depends on the BDE has been moved from DB.PAS into DBTABLES.PAS. Existing code may have to be edited to convert references to variables of TDataSet to TDBDataSet or TBDEDataSet. The following methods and properties have been moved from TDataSet to TDBDataSet or TBDEDataSet:

       procedure GetIndexInfo;

       procedure FetchAll;

       procedure ApplyUpdates;

       procedure CommitUpdates;

       procedure CanelUpdates;

       procedure RevertRecords;

       function UpdateStatus: TUpdateStatus;

       property UpdatesPending: Boolean;

       property Handle: HDBICur;

       property Locale: TLocale;

       property UpdateObject: TDataSetUpdateObject;

       property UpdateRecordTypes: TUpdateRecordTypes;

       property ExpIndex: Boolean;

       property KeySize: Word;

       property SetDetailFields;

Except for Handle, Locale, ExpIndex, KeySize, and SetDetailFields, these methods and properties involve code that processes cached updates.

In addition to these changes, a new function, IsBlob, is added to TField:

       function IsBlob: Boolean;

         {Returns True if a field is a blob field}

Finally, in TField, BDECalcField is renamed ServerCalcField.


Borland Database Engine and SQL Links Features

This field test ships with new versions of the BDE and SQL Links. For information about BDE and SQL Links 3.5, see Readlink.txt.

To configure the BDE, use the BDE Configuration Utility (BDEADMIN.EXE) located in \Program Files\Borland\Delphi 3.0\BDE.

A version of the BDE that supports multibyte characters is included with this field test. The multibyte-enabled BDE is in a separate directory and is not installed with Ivory. (In the release version of the product, the multibyte BDE will be standard.) Files related to the multibyte BDE are in the MBCS directory on the Ivory CD. For more information, see BDEMBCS.TXT.

New BDE features include:

FoxPro support

The dBASE driver now includes support for FoxPro compressed index (.CDX) and BLOB (.FPT) files, letting you open and create FoxPro tables. To create a FoxPro table with the dBASE driver, set the LEVEL driver configuration parameter to 25 using the Configuration page of the Database Explorer. To create FoxPro tables using the BDE API, pass the optional parameter LEVEL with a value of 25 when using DbiCreateTable to create a dBASE table. To add a FoxPro index, set curTABLELEVEL to 25 using DbiSetProp before calling DbiAddIndex. DbiSetProp will return an error if the table is already using dBASE index or BLOB files. To see if a cursor is referencing a FoxPro table, retrieve CURProps using DbiGetCursorProps and check if CURProps.iTblLevel is equal to FOXLEVEL25.

Microsoft Access support

The BDE can now open or create Microsoft Access tables using the MSACCESS driver. In the BDE API, use the constant szMSACCESS when creating Access tables or checking the table type. The following table lists new physical data types for MSACCESS and their BDE logical equivalents:

Physical	Logical

    --------------------------------------

fldACCTEXT	fldZSTRING

fldACCDATETIME	fldTIMESTAMP

fldACCBIT	fldBOOL

fldACCMONEY	fldFLOAT, fldstMONEY

fldACCSHORT	fldINT16

fldACCLONG	fldINT32

fldACCFLOAT	fldFLOAT

fldACCLONGTEXT	fldBLOB, fldstMEMO

fldACCLONGBINARY	fldBLOB, fldstBINARY

fldACCAUTOINC	fldINT32, fldstAUTOINC

fldACCBYTE	fldUINT16

OLE DB support

OLE DB providers installed on your system are automatically added to the BDE configuration, and appear as individual BDE drivers. In the BDE API, use szOLEDB when creating OLE DB tables or checking the source of a table.

Parameter binding support for BLOBs and long strings (greater than 255 characters)

BDE now supports parameter binding for BLOBs and strings longer than 255 characters. Both types of data use the new data structure BLOBParamDesc to describe the parameter in a FLDDesc structure:

typedef struct

    {

       pBYTE   pBlobBuffer;  // Blob buffer (client)

       UINT32  ulBlobLen;    // Length of the blob.

       UINT16  iUnUsed[4]

    } BLOBParamDesc;

    typedef BLOBParamDesc *pBLOBParamDesc;

Clients need to allocate and set up a BLOBParamDesc structure for each blob or long string that will be passed as a parameter. For BLOBs, iFldType in FLDDesc is set to fldBLOB and iSubType is set to fldstMEMO or fldstBINARY. For long strings, iFldType should be set to fldZSTRING and the length should specified in iUnits1 as normal. The BLOBParamDesc structure is copied into the parameter buffer at the specifed iOffset, then DbiQSetParams and DbiQExec are called as normal.


New Property: curGETHIDDENCOLUMNS

When curGETHIDDENCOLUMNS is set to True using DbiSetProp, functions like DbiGetCursorProps, DbiGetFieldDescs, DbiGetField, and DbiPutField see information for all fields  from the underlying tables used by the query, instead of only the fields selected by the query. For example, consider the following table (Country) and SQL query:

    Name       Capital        Area       Population

    Argentina  Buenos Aires   2,777,815  32,300,003

    Bolivia    La Paz         1,098,575  7,300,000

    Brazil     Brasilia       85,111,968 150,400,000

    Canada     Ottawa         9,976,147  26,500,000

    Chile      Santiago       756,943    13,200,000

    Colombia   Bogota         1,138,907  33,000,000

    SELECT Name,Population

           FROM Country

           WHERE Population>15000000

    Using the query when curGETHIDDENCOLUMNS was False would

    produce the following result:

    Name      Population

    Argentina 32,300,003

    Brazil    150,400,000

    Canada    26,500,000

    Colombia  33,000,000

    If curGETHIDDENCOLUMNS was True, all underlying columns

    would be included, resulting in

    Name       Capital        Area       Population

    Argentina  Buenos Aires   2,777,815  32,300,003

    Brazil     Brasilia       85,111,968 150,400,000

    Canada     Ottawa         9,976,147  26,500,000

    Colombia   Bogota         1,138,907  33,000,000


Database Explorer Enhancements

When you launch the Database Explorer from within Ivory, context-sensitive online Help is not available. Use the standalone version of Database Explorer, DBEXPLOE.EXE, to access full online help.

The final release of Database Explorer will include a page tab for configuring the Borland Database Engine. For this field test, configure the BDE by running the BDE Configuration utility (BDEADMIN.EXE).

The following features have been added or enhanced in the SQL Database Explorer for this field test:

Editing SQL Objects

Editing of some SQL objects (e.g., views and stored procedures for all servers, Oracle packages and package bodies, InterBase generators and exceptions) is now supported.

Object menu commands that affect SQL objects include New, Rename, Delete, Apply, Cancel, and Save As. Remember that edits are temporary until they are applied.

View|Change Text Option

As you edit an SQL object, you can see the queries that will be executed when you apply your changes. The queries are listed in a read-only Changes area on the Text page. To show the Changes area, select View|Change Text.

SQL Editing in the IDE Code Editor

If you invoke the SQL Database Explorer from the IDE, you can use the Code editor and its rich set of editing features to edit SQL objects. To use the code editor:

1. Invoke the SQL Database Explorer from the IDE.

2. Select the SQL object to edit.

3. Choose Edit Text (Ctrl-T) from the Explorer menu.

While you edit an SQL object in the code editor, the object's text is read-only in the Database Explorer.

Query Options

A new Query Options dialog (select Options | Query) sets options for queries executed on the Enter SQL page. The Live Queries option has been moved from the Options menu to the Query Options dialog. New options control the execution of query scripts, allowing you to configure the Database Explorer for limited compatibility with tools from server vendors such as Sybase isql.exe and Microsoft SQL Server saf.exe. (For example, to configure the Database Explorer for use with Sybase isql.exe, you would set the line delimiter to "go".) By default, the line delimiter is "---" and no statement delimiter is defined; a typical statement delimiter would be ";".

Save As Dialog

A new Save As menu item invokes a Save As dialog where you can specify a name for the SQL object to save. By default an object's current name is proposed. If you keep the current name, any changes you have made are applied to the current object.

If you specify a new name, a new object is created and any editing changes are applied to the new object. Edits to the original object remain intact, but are not applied to the original object until you choose to apply them.

Saving Text to Files

If an SQL object has a text representation on the Text page, the local menu contains a Save Text to File command that enables you to save the object's text to a text file.

You can also save a change script to a text file, which can be useful when you want to reuse the script on different systems with the same SQL object. To save a change script, click or tab into the change script and choose Save Text to File. Unless the change script is selected, choosing Save Text to File saves the object, not the script.

Data Page Local Menu Commands

The following commands have been added to the local menu for the Data page:

1. Order

2. Range

3. Filter

These commands invoke dialogs that let you specify data ordering, set a range, or filter a data set. When you explore a different SQL object, Order, Range, and Filter are automatically reset.

Object-specific Local Menus

Local menus now have object- and platform-specific commands for some objects. For example, when you are editing an Oracle package heading, function, or procedure, a Compile command appears on the local menu.

Filter and Transaction Control

The Enter SQL local menu provides the following commands for controlling filtering and transactions:

1. Filter

2. Begin Transaction (or Commit Transaction and

    Rollback Transaction)

3. Isolation

Filter invokes a dialog that lets you apply a filter to the data in a query's result set.

Begin Transaction lets you start a transaction. When you start a transaction, the Begin Transaction menu item is disabled, and the Commit Transaction and Rollback Transaction items are enabled on the menu.

Isolation lets you specify an isolation level for a transaction before you start it. Once you begin a new transaction this item is disabled until you commit or roll back the transaction. For more information about transaction isolation levels, see the Delphi online help.

Drag-and-Drop Stored Procedures

Stored procedures are now available as a drag-and-drop source. You can create a TStoredProc component by dragging a stored procedure from the Database Explorer onto a Delphi form, just as you would with databases, tables, views,  and columns.

Support for DB2 and Informix

The Database Explorer now supports DB2 for common servers and Informix 5, 6, and 7. DB2 support is also planned for MVS and AS/400.

Word Wrap

A Word Wrap option has been added to the Options menu. Select Word Wrap if you want text to wrap on the Text page. This option is especially useful for DB2 and Informix servers, which don't save the original line breaks for objects such as views.


Internet development features

Internet Components

New components have been added to enable the creation of Internet server DLLs. See the online Help included with this field test for more information on Internet components.

 To debug a WebServer application using Microsoft IIS server, set the application's run parameters as follows:

   Host Application: c:\winnt\system32\inetsrv\inetinfo.exe

   Run Parameters:   -e w3svc

This starts the IIS Server and allows you to debug your ISAPI DLL.

Under Windows NT, you must have the correct user rights to debug a DLL. In the User Manager, (1) choose Policies|User Rights, (2) select "Show advanced user rights", (3) choose  "Act as part of the operating system" in the "Right" list, and (4) add your name to the "Grant To" list.


Compiler changes

Package Support

Language extensions and compiler directives have been added to support packages. See the online Help for details.

Interfaces

Language extensions have been added to support development of COM and ActiveX objects. See the online Help for details.

Assertions

Assert is a standard procedure, declared in the System unit, that tests whether a Boolean expression is successful:

       Assert(expr: Boolean [; msg: string]);

If the Boolean test fails Assert raises an exception. The definition for Assert(x) in the System unit is similar to the following Pascal code:

       if not x then

         raise EAssertionFailed;

Assertions are a debugging tool. Use them to test for preconditions that should not fail. Assert provides an opportunity to intercept a condition that should halt a program and that might otherwise cause abnormal termination.

EAssertionFailed is declared in the Sysutils unit:

   EAssertionFailed = class(Exception);

If no user code catches the EAssertionFailed, then the RTL will issue runtime error 227 and halt the program.

Since assertions are not usually used at run-time in shipping versions of a product, compiler directives are provided that disable the generation of code for assertions. They are:

       $ASSERTIONS ON/OFF	(long form)

       $C +/- (short form)

These are global switches and take effect for the whole source file in which they are contained, regardless of their position in the file.  It is not possible to enable and disable assertions for something smaller than a source file.


Boolean Data Types

For compatibility with Visual Basic, the True value for data types ByteBool, WordBool, and LongBool is now represented as -1; False is still represented as 0. Internally, the compiler gives these data types the same range as Integer. Every effort has been made to minimize the code revision required to accommodate this change. However, bounds checking is no longer implemented for these types. If typecasting has been used, code may no longer work properly. Short-circuit evaluation functions as before.

The Boolean data type remains unchanged (True = 1, False = 0).


Wide string types

The wide-string-type is denoted by the predefined identifier WideString. The wide string type represents a string composed of Unicode characters. Unicode is a 16-bit character set system. ANSI characters, those which are used by short strings and long strings, are 8 bits wide.

Since the 32-bit version of OLE uses Unicode for all strings, strings must be of wide string type in any ActiveX application.

As with long strings, wide strings are dynamically-allocated with a maximum length limited only by available memory. However, wide strings are not reference counted -- the dynamically-allocated memory that contains the string is deallocated when the wide string goes out of scope.

In all other respects wide strings possess the same attributes as long strings.

A wide string variable occupies four bytes of memory which contain a pointer to a dynamically allocated string. When a wide string variable is empty (contains a zero-length string), the string pointer is nil and no dynamic memory is associated with the string variable. For a non-empty string value, the string pointer points to a dynamically allocated block of memory that contains the string value in addition to a 32-bit length indicator. The table belowshows the layout of a wide string memory block.

Offset	Contents

-4	32-bit string length

0..Length - 1	Character string

Length	NULL character

The NULL character at the end of a wide string memory block is automatically maintained by the compiler and the built-in string handling routines. The presence of the NULL character makes it possible to directly typecast a wide string to a null-terminated string.


Exit procedures

Exit procedures can only be used when generating an .EXE file. When using packages, exit behavior must be implemented in a finalization section. It is recommended that finalization sections be used for all exit behavior.


Out parameters

The declaration of a procedure or function specifies a formal parameter list. Ivory introduces a new type of paramenter.

An out parameter is used when the called COM procedure or function parameter will exclusively return output data. Out parameters are resource managed; an out parameter reference is freed automatically by the compiler when the procedure or function is called. When a COM method with an out parameter is called, it simply fills the parameter with new information and on failure will set the parameter to nil.

Note:

It is recommended that you only use out parameters in COM methods since var parameters are more efficient than out parameters. In all other respects an out parameter behaves like a var parameter.


Safecall calling convention

Ivory supports a new type of calling convention called safecall which is similar to the stdcall calling convention.

Parameters are transferred to procedures and functions via CPU registers and/or the stack, depending on the calling conventions of the procedures and functions. Ivory now supports five calling conventions: register, cdecl, pascal, stdcall and safecall.

The safecall convention passes parameters from right to left.

The safecall calling convention is used to implement methods of dual interfaces. All methods of a dual interface (except from those inherited from IUnknown and IDispatch) must use the safecall calling convention.


Interfaces

Delphi 3.0 extends Object Pascal to add the concept of object interfaces as a native part of the language.

An object interface defines a set of methods that can be called on an object from any code outside that object's implementation. Like a unit interface, an object interface allows client code to access the object without needing to know anything about the object's implementation. The object's implementation class can remain hidden in the unit that implements it.

In Delphi, object interfaces are also compatible with the ActiveX Component Object Model (COM) specification for interfaces. This means that any object built with Delphi that supports one or more interfaces is automatically an ActiveX COM object. COM specifies a language-independent binary protocol for objects to interact with each other. The set of interfaces the object implements define a contract that allow COM objects to interact. Delphi objects with interfaces can interact with COM objects implemented in C++, Basic and other languages.

The declaration of an interface does not imply an actual implementation of the interface. Interfaces are never implemented by themselves, rather they are always implemented as part of a class. Classes that implement interfaces are instantiated as COM objects.

An interface reference is a reference to an implementation of an interface. Through an interface reference it is possible to invoke methods of a given interface without any knowledge of the actual implementation of the interface. Only methods declared in the interface may be called using the interface reference.


Interface syntax

The following is an example of an interface declaration:

type

  IMalloc = interface(IUnknown)

    ['{00000002-0000-0000-C000-000000000046}']

    function Alloc(Size: Integer): Pointer; stdcall;

    function Realloc(P: Pointer; Size: Integer): Pointer; stdcall;

    procedure Free(P: Pointer); stdcall;

    function GetSize(P: Pointer): Integer; stdcall;

    function DidAlloc(P: Pointer): Integer; stdcall;

    procedure HeapMinimize; stdcall;

  end;

The following rules apply when declaring an interface:

Interfaces can only be declared in a type declaration section in the outermost block of a module.

An interface cannot be declared in a procedure or function.

An "unnamed" interface type cannot be declared in a variable or constant declaration.

Fields (instance variables) cannot be declared in an interface.


IUnknown and inheritance

The declaration of an interface may specify an ancestor interface. If no ancestor is specified, the interface is a direct descendant of IUnknown, the built-in ultimate ancestor of all interfaces. The group of methods identified by an interface consists of the methods declared by IUnknown, the methods declared by each ancestor interface, and the methods declared by the interface itself.

Inheritance in COM does not mean code reuse. Because implementations are not associated with interfaces, interface inheritance does not mean code inheritance. It means only that the contract associated with an interface is inherited in a pure-virtual base-class fashion and modified either by adding new methods or by further qualifying the allowed usage of methods. There is no selective inheritance in COM: If one interface inherits from another, it includes all the methods that the other interface defines.

Inheritance is used sparingly in the predefined COM interfaces. All predefined interfaces inherit their definitions from the interface IUnknown, which contains three vital methods: QueryInterface, _AddRef, and _Release. All COM objects must implement the IUnknown interface because it provides the means to move freely between the different interfaces that an object supports with _QueryInterface, and to manage its lifetime with _AddRef and _Release.

The IUnknown standard interface is declared by the System unit as follows:

type

  IUnknown = interface

    ['{00000000-0000-0000-C000-000000000046}']

    function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;

    function _AddRef: Integer; stdcall;

    function _Release: Integer; stdcall;

  end;


Interface identification

An interface can optionally include an interface identifier which consists of a string constant enclosed in square brackets immediately preceding the elements of the interface. The string constant given in an interface identifier must evaluate to a globally unique identifier (GUID) of the form

'{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'

where each x is a hexadecimal digit (0 through 9 or A through F).

When an interface includes an interface identifier, it becomes possible to obtain references to implementations of the interface through interface querying. Interface querying.


Calling conventions

The default calling convention for methods of an interface is register.

Interfaces intended to be used between modules, in particular modules written in different programming languages, should declare all methods with stdcall.

The safecall calling convention is used to implement methods of dual interfaces. This is described further in a following section.


Member visibility

All members (methods and properties) of an interface have public visibility. The private, protected, public, published, and automated class visibility specifiers are not allowed in an interface.


Access specifiers

Properties declared in an interface must include a read access specifier, a write access specifier, or both. Since an interface cannot contain fields, the access specifiers are only allowed to reference methods of the interface. A default directive may be used to mark an array property as the default property of the interface.


Forward declarations

An interface declaration consisting only of the reserved word interface is a forward declaration. A forward declaration must be resolved by a normal declaration of the interface within the same type declaration part. Forward declarations allow mutually dependent interfaces to be declared. For example:

type

  IControl = interface;

  IWindow = interface

    ['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']

    function GetControl(Index: Integer): IControl;

    ...

  end;

  IControl = interface

    ['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']

    function GetWindow: IWindow;

    ...

  end;


Globally unique identifiers

The declaration of an interface can optionally include an interface identifier which consists of a string constant enclosed in square brackets immediately preceding the elements of the interface. The string constant given in an interface identifier must evaluate to a globally unique identifier of the form

'{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'

where each x is a hexadecimal digit (0 through 9 or A through F).

A globally unique identifier, also known as a GUID, is a 16-byte binary value that uniquely identifies an interface amongst all other interfaces.

The following types declared in the System unit are used to manipulate GUIDs.

type

    PGUID = ^TGUID;

    TGUID = record

      D1: Integer;

      D2: Word;

      D3: Word;

      D4: array[0..7] of Byte;

    end;

When declaring a typed constant of type TGUID, a string constant of the above form may be specified as the value of the GUID. For example

const

  IID_IMalloc: TGUID = '{00000002-0000-0000-C000-000000000046}';

When calling a procedure or function, an interface identifier may be specified in place of a parameter expression for a value or constant parameter of type TGUID. For example

function Supports(Unknown: IUnknown; const IID: TGUID): Boolean;

can be called in the following fashion

if Supports(Allocator, IMalloc) then ...


Interface implementations

Once an interface has been declared it must be implemented in a class before it can be used. Any Object Pascal class can implement interfaces.


Class declaration

The interfaces implemented by a class are specified by following the ancestor class identifier with a comma-separated list of interface identifiers, for example:

type

  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)

    ...

  end;

For example the TInterfacedObject class, which implements the IUnknown interface, is declared in the System unit as follows:

type

  TInterfacedObject = class(TObject, IUnknown)

  private

    FRefCount: Integer;

  rotected

    function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;

    function _AddRef: Integer; stdcall;

    function _Release: Integer; stdcall;

  public

    property RefCount: Integer read FRefCount;

  end;

TInterfacedObject descends directly from TObject, a class that does not implement interfaces.

Classes that implement interfaces can also be used as base classes. For example, the following declaration:

type

  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)

    ...

  end;

specifies that the TMemoryManager class is a direct descendent of TInterfacedObject and it that implements the IMalloc and IErrorInfo interfaces.


Interface method implementation

When a class specifies one or more interfaces in ancestor list, that class must provide the implementation for the methods of each interface. To implement the interface, the interface methods must be either implicitly or explicitly mapped onto compatible methods in the class.

Mapping interface methods

An interface is implemented by mapping the methods of the interface onto compatible methods in the class. A class can successfully implement an interface only if each and every method of the interface can be mapped onto a compatible method in the class.

For an interface method to be considered compatible with a class method, the methods must adhere to the following rules:

methods must have the same number of parameters

parameters in corresponding positions must be of identical types

the result types (if any) must be identical

the calling conventions must be the same

By default, each method in an interface is mapped onto a compatible method of the same name in the implementing class. For example, given the following declarations of the IMalloc interface:

type

  IMalloc = interface(IUnknown)

    ['{00000002-0000-0000-C000-000000000046}']

    function Alloc(Size: Integer): Pointer; stdcall;

    function Realloc(P: Pointer; Size: Integer): Pointer; stdcall;

    procedure Free(P: Pointer); stdcall;

    function GetSize(P: Pointer): Integer; stdcall;

    function DidAlloc(P: Pointer): Integer; stdcall;

    procedure HeapMinimize; stdcall;

  end;

TMemoryManager must implement or inherit the following methods for it to successfully implement IMalloc.

function QueryInterface(const IID: TGUID; out Obj): Integer; stdcall;

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

function Alloc(Size: Integer): Pointer; stdcall;

function Realloc(P: Pointer; Size: Integer): Pointer; stdcall;

procedure Free(P: Pointer); stdcall;

function GetSize(P: Pointer): Integer; stdcall;

function DidAlloc(P: Pointer): Integer; stdcall;

procedure HeapMinimize; stdcall;

Since every interface inherits from the IUnknown standard interface, an object that implements interfaces is always required to implement the QueryInterface, _AddRef, and _Release methods defined by IUnknown. The TInterfacedObject class defined in the System unit provides an implementation of these methods and is thus a convenient base class for classes that implement interfaces.

Method resolution clauses

The default name-based interface method mapping can be overridden by including interface method resolution clauses in the class declaration. Such clauses allow interface methods to be mapped onto compatible class methods with differing names.

For example, the declaration:

type

  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)

    function IMalloc.Alloc = Allocate;

    procedure IMalloc.Free = Deallocate;

    ...

  end;

specifies that the Alloc and Free methods of the IMalloc interface should map onto the Allocate and Deallocate methods of TMemoryManager.

A class method identifier specified in an interface method resolution clause must name a method in the class or an ancestor of the class. In effect this means that an interface method resolution clause is allowed to make a forward reference to a method identifier that is declared later on within the same class declaration. For example, in the following declaration:

type

  TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)

    function IMalloc.Alloc = Allocate;

    procedure IMalloc.Free = Deallocate;

    ...

    function Allocate;

    procedure Deallocate;

  end;

the TMemoryManager class methods, Allocate and Deallocate, are declared after the interface method resolution clauses.

When two or more interfaces have a method with the same name, use the method resolution clause to resolve name conflicts. Declare and implement a new method under a different name, and then map the interface method to this new method. For example:

type

  IWindow = interface

    procedure Draw;

    ...

  end;

  ICanvas = interface

    procedure Draw;

    ...

  end;

  TWindow = class(TInterfacedObject, IWindow, ICanvas)

    procedure IWindow.Draw = Drawing;

    ...

    procedure Drawing;

   procedure Draw;

   end;

The IWindow interface Draw method is mapped to a method of a different name in TWindow. The Drawing method of TWindow implements the Draw method of IWindow, whereas the Draw method of TWindow implements the Draw method of the ICanvas interface.

Rules of method resolution clauses

When mapping methods in inherited classes, the rules for method resolution clauses can be affected by the mapping and type of methods in the base class, and by a reimplementation of the interface in the inherited class.

 An interface method cannot be mapped onto an inherited private method of an ancestor class declared in another module.

When mapping interface methods onto class methods, the compiler excludes any methods that are not visible in the class being implemented. This in effect means that an interface method cannot be mapped onto an inherited private method of an ancestor class declared in another module.

 Interface method resolution clauses cannot alter the mapping of methods of interfaces that were introduced by ancestors of the class.

The mapping of an interface method onto a method of a class is established once and for all in the class that introduces the interface, and interface method resolution clauses can only be applied to interfaces that are introduced by the class in which the interface method resolution clauses are listed.

 Subclasses are allowed to change the implementation of an interface method only if the interface method was mapped onto a virtual or dynamic method

When a virtual or dynamic method is overridden, then all interface methods that are mapped onto the virtual or dynamic method are affected by the override. Only by mapping an interface method onto a virtual or dynamic method can a class allow subclasses to change the implementation of the interface method. For example:

type

  IWindow = interface

    ['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']

    procedure Draw;

    ...

  end;

  TWindow = class(TInterfacedObject, IWindow)

    procedure Draw; virtual;

    ...

  end;

  TFrameWindow = class(TWindow)

    procedure Draw; override;

    ...

  end;

Here, a call to the Draw method of an IWindow interface obtained from an instance of TWindow will invoke TWindow.Draw, and a call to the Draw method of an IWindow interface obtained from an instance of TFrameWindow will invoke TFrameWindow.Draw.

 A class is allowed to reimplement an interface that was inherited from a base class

When a class implements a particular interface, that implementation effectively hides any inherited implementation of the same interface. For example:

type

  IWindow = interface

    ['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']

    procedure Draw;

    ...

  end;

  TWindow = class(TInterfacedObject, IWindow)

    procedure Draw;

    ...

  end;

  TFrameWindow = class(TWindow, IWindow)

    procedure Draw;

    ...

  end;

Here, TFrameWindow's implementation of the IWindow interface hides the implementation that was inherited from TWindow. Also, since TFrameWindow's Draw method hides the Draw method that was inherited from TWindow, each IWindow interface will have its own implementation of Draw.

 Inherited interfaces are not affected by reimplementations

The reimplementation of an interface is completely separate from the inherited implementation that it hides, and the interface method resolution clauses (if any) that were used to resolve the inherited interface have no effect on the reimplemented interface.

When a class implements an interface, it does not implicitly implement any ancestor interfaces of that interface. A class only implements the interfaces that are specifically named in the class declaration and any ancestor class declarations.

type

  IControl = interface

    ...

  end;

  IWindow = interface(IControl)

    ...

  end;

  TWindow = class(TInterfacedObject, IWindow)

    ...

  end;

Here, since IWindow is a descendant of IControl, an IWindow interface instance can also function as an IControl instance. However, given that TWindow does not specifically list IControl as an implemented interface, a request for a TWindow's IControl interface would fail.

Since every interface inherits from the IUnknown standard interface, an object that implements interfaces is always required to implement the QueryInterface, _AddRef, and _Release methods defined by IUnknown. The TInterfacedObject class defined in the System unit provides an implementation of these methods and is thus a convenient base class for classes that implement interfaces. TInterfacedObject is described further in a following section.


Interface references

A variable can be declared that is a reference to an interface. An interface reference is a reference to an implementation of an interface. Through an interface reference it is possible to invoke methods of a given interface without any knowledge of the actual implementation of the interface.


Reference counting

Interface references are reference counted. Through reference counting it is possible for the implementation of an interface to know how many references exist to the interface. Further, reference counting makes it possible for the implementation of an interface to dispose of the underlying object(s) once the reference count reaches zero.

Interface reference counting is entirely automatic in Object Pascal, and is implemented by calling the _AddRef and _Release methods that every interface inherits from IUnknown. When an interface variable is assigned a new interface reference, the reference count of the previous interface reference (if any) is automatically decremented by calling _Release, and the reference count of the new interface reference (if any) is automatically incremented by calling _AddRef. Likewise, when an interface variable goes out of scope, the reference count of the referenced interface is automatically decremented by calling _Release.


Type compatibility

Two interface types are compatible if they are identical or if one is derived from the other. Furthermore, the value nil is compatible with any interface type. The relational operators = and <> can be used to compare compatible interface types.

An interface type T2 is assignment compatible with an interface type T1 (that is, T1 := T2 is allowed) if T2 is identical to or derived from T1. In other words, a reference of a particular interface type can also function as a reference of a base type of that interface type.

A class type is assignment compatible with any interface type implemented by the class type. In other words, if a class type T implements (or inherits implementations of) interface types I1 and I2, then T is assignment compatible with I1 and I2.

Any interface type is assignment compatible with the Variant type. If the interface is of type IDispatch or a descendant, the resulting Variant value has a type code of varDispatch. Otherwise, the resulting Variant value has a type code of varUnknown.

The Variant type is assignment compatible with the IUnknown and IDispatch types. The type code of a Variant must be varEmpty, varUnknown, or varDispatch for it to be successfully converted to an IUnknown, and varEmpty or varDispatch for it to be successfully converted to an IDispatch. Otherwise, an exception is raised.


Interface conversions

Interface types follow the same rules as class types in variable and value type casts.

A value of a class type can be explicitly cast to an interface type using a typecast of the form IntfType(X), where X is an expression of a class type and IntfType is the type identifier of an interface implemented by the class type.

A value of an interface type can be explicitly cast to type Variant using a typecast of the form Variant(X), where X is an expression of an interface type. If X is of type IDispatch or a descendant, the resulting variant type code is IDispatch. Otherwise the resulting type code is IUnknown.

A Variant value can be explicitly cast to type IUnknown and type IDispatch using typecasts of the form IUnknown(X) and IDispatch(X), where X is an expression of type Variant. The type code of a Variant must be varEmpty, varUnknown, or varDispatch for it to be successfully converted to an IUnknown, and varEmpty or varDispatch for it to be successfully converted to an IDispatch. Otherwise, an exception is raised.


Using assignment to obtain an interface reference

As stated in the type compatibility rules above, an object is assignment compatible with any interfaces implemented by the class of the object. This means that references to interfaces on an object can be obtained through simple assignments. For example

type

  IWindow = interface

    ...

  end;

  IDragDrop = interface

    ...

  end;

  TEditWindow = class(TObject, IWindow, IDragDrop)

    ...

  end;

var

  EditWindow: TEditWindow;

  Window: IWindow;

  DragDrop: IDragDrop;

Given the above declarations, simple assignments can be used to obtain the IWindow and IDragDrop interfaces of a TEditWindow object.

Window := EditWindow;

DragDrop := EditWindow;

Attempting to convert an object to an interface type that isn't implemented by the object produces a compile time error. Note that the conversion of an object to an interface type is based on the declared type of the object, not the actual type. For example

var

  Instance: TObject;

  Window: IWindow;

begin

  Instance := TEditWindow.Create(...);

  Window := Instance;

  ...

end;

The above assignment of Instance to Window will produce a compile time error because the declared type of Instance is TObject (which doesn't implement IWindow) even though the actual type of Instance is TEditWindow. The as operator can be used to obtain an interface from an object based on the actual type of the object. This is described in a following section.

In cases where an application interacts with an object solely through interfaces, the lifetime of the object is effectively controlled by the automatic reference counting of its interfaces. This means that there is no need to manually destroy the object---it will be automatically destroyed when the last interface reference goes out of scope. For example

procedure CreateEditWindow;

var

  Window: IWindow;

begin

  Window := TEditWindow.Create(...);

  ...

end;

In the above example, upon returning, the CreateEditWindow procedure will automatically call Window._Release which, assuming that no other interfaces have been obtained from the object, will destroy the TEditWindow object.


Interface querying

Interface references can be produced from object references and from other interface references through interface querying using the as operator. An interface querying operation takes the form Reference as Interface, where Reference is an expression of a class type that implements the IUnknown interface or an expression of any interface type, and Interface is an interface type identifier. The result of such an operation is an interface reference of the given interface type. If the object or interface being queried does not implement the given interface, an exception is raised.

The as operator can be used to query an object for an interface only if the object implements the IUnknown interface. An operation of the form Reference as Interface, where Reference is an object, is effectively transformed into IUnknown(Reference) as Interface, which will only succeed if the object implements IUnknown.

The operation Reference as Interface is evaluated as follows

If Reference is nil, the resulting value is nil.

If Reference is not nil, the QueryInterface method of Reference is called with the GUID of the Interface type. If QueryInterface returns a non-zero value, indicating failure, an exception is raised. Otherwise, the resulting value is the new interface reference returned by QueryInterface.


Interactions with other language features

It is possible to declare initialized variables of interface types, but the only allowed initial value for such variables is nil.

The Assigned standard function may be passed an expression of an interface type. The result is False if the interface is nil, or True otherwise.

A with statement may specify an expression of an interface type.

An expression of an interface type may be used as an element of a type variant open array constructor. The corresponding value type code of such an element is vtInterface.

An OLE Automation method call parameter can be of an interface type.


OLE automation Interface types

Objects that implement the IDispatch interface are OLE automation objects. OLE automation introduces the concept of dispatch interfaces and dual interfaces.


Dispatch interfaces

Dispatch interface declarations are used to describe the methods and properties an OLE Automation object implements through its IDispatch interface. Whereas regular interfaces use direct v-table dispatching, calls to methods of a dispatch interface are routed through the Invoke method of the underlying IDispatch implementation.

Dispatch interfaces are characterized by the following restrictions:

It is not possible for a class to list a dispatch interface as one of its implemented interfaces. Since methods of dispatch interfaces are really just prototypes for calls to the Invoke method of the underlying IDispatch implementation, dispatch interfaces are never actually implemented.

The declaration of a dispatch interface cannot specify an ancestor interface.

Method declarations in a dispatch interface do not allow any interface method directives such as stdcall to be specified.

All function result and parameter types must be OLE Automation compatible.

When compiling dispatch interface method calls, the compiler generates a call descriptor which it passes to a call dispatcher in the run-time library along with the parameters of the method call. The call descriptor type and the run-time library call dispatcher are declared in the System unit as follows:

type

  PDispCallDesc = ^TDispCallDesc;

  TDispCallDesc = packed record

    DispID: Integer;

    ResType: Byte;

    CallType: Byte;

    ArgCount: Byte;

    NamedArgCount: Byte;

    ArgTypes: array[0..*] of Byte;

  end;

procedure _IntfDispCall(ResultAddr: Pointer; const Dispatch: IDispatch; DispCallDesc: PDispCallDesc; ...); cdecl;

On entry to _-IntfDispCall, the stack contains, in increasing address order, the ResultAddr, Dispatch, and DispCallDesc parameters followed by the actual parameters of the dispatch method call. The caller is responsible for removing all parameters from the stack after the call.

For a dispatch method that returns a function result, the ResultAddr parameter contains a pointer to a location that will receive the function result, and the ResType field of the call descriptor gives the type of the function result. The function result location is assumed to be uninitialized (i.e. any previous value must first be finalized by the compiler). For a dispatch method that has no function result, or for a dispatch method whose function result is ignored, the ResultAddr parameter is nil and the ResType field of the call descriptor is zero.

The contents of the call descriptor given by the DispCallDesc parameter are outlined in table X.X.

Field	Description

DispID	Dispatch ID of the method being called.

ResType	Variant type code of the expected function result, or 0 for no function result.

CallType	Call type: 1 for method call, 2 for property get, 4 for property set.

ArgCount	Number of arguments, including the value argument for a property set operation.

NamedArgCount	Reserved, always 0.

ArgTypes	Variant type code for each argument. For var and out parameters, the high bit ($80) is set.

The dispatch method call parameters are pushed on the stack using normal cdecl conventions.


Dual interfaces

A dual interface is an interface that supports both compile-time v-table binding and run-time dynamic binding through OLE Automation. A dual interface must descend (directly or indirectly) from the IDispatch interface, which is declared in the System unit as follows:

type

  IDispatch = interface(IUnknown)

    function GetTypeInfoCount(out Count: Integer): Integer; stdcall;

    function GetTypeInfo(Index, LocaleID: Integer;

      out TypeInfo): Integer; stdcall;

    function GetIDsOfNames(const IID: TGUID; Names: Pointer;

      NameCount, LocaleID: Integer; DispIDs: Pointer): Integer; stdcall;

    function Invoke(DispID: Integer; const IID: TGUID;

      LocaleID: Integer; Flags: Word;

      Params, VarResult, ExcepInfo, ArgErr: Pointer): Integer; stdcall;

  end;

All methods of a dual interface (except from those inherited from IUnknown and IDispatch) must use the safecall calling convention, and all function result and parameter types must be OLE Automation compatible.


TActiveXControl

TActiveXControl is an abstract class that provides the functionality to embed a VCL control into an ActiveX Container window.

TActiveXControl defines the core behavior and interfaces required of an ActiveX control and connects that behavior to any VCL control derived from TWinControl. This allows the control to be embedded in any ActiveX Container, including Internet Explorer, Visual Basic, PowerBuilder, and of course Delphi.

To create a new ActiveX control class, use the ActiveExpert. The steps for building an ActiveX control are covered in depth in the Programmer’s Guide.

The TActiveXControl class is derived from TAutoObject, therefore the object resulting is a COM object that supports OLE automation, a type library, and has a factory. In addition, TActiveXControl implements the following behaviors:

embedding in a container (site)

in-place activation

persistence

events

property pages

property browsing

TActiveXControl is an abstract class, and so cannot be instantiated.  Instead, you must derive a new class from TActiveXControl to implement an ActiveX control. This derived class is called an implementation class.  An implementation class derived from TActiveXControl normally implements an automation interface as well, to specify the object’s properties and methods.

Ordinarily, you do not create an instance of an implementation class directly. Instead, you create a factory object in the initialization section of the unit that contains the class declaration. The factory object will then create the ActiveX control object when asked to by the system.

TActiveXControl Interfaces

In addition to the interfaces inherited from TAutoObject, TActiveXControl implements the following interfaces:

IPersistStreamInit

IPersistStorage

IOleObject

IOleControl

IOleInPlaceObject

IOleInPlaceActiveObject

IViewObject

IViewObject2

IPerPropertyBrowsing

ISpecifyPropertyPages

TActiveXControl Properties

Control (TWinControl)

The control property is a pointer to the VCL implementation of the ActiveX control.

TActiveXControl Protected Virtual Methods

DefinePropertyPages

procedure DefinePropertyPages( DefinePropertyPage: TDefinePropertyPage);

This method is called when the control is about to show its property pages.  If you want your control to show any property pages, override this method and call the DefinePropertyPage callback function once for each property page to be displayed.  If you do not override this method, no property pages will be shown.

DefinePropertyPage takes one parameter, which is the class id of the property page object that should be added to the property dialog.

An ActiveX property page dialog is a tabbed dialog that is used to edit the properties of one or more ActiveX objects.  Each property page is implemented as a COM object, and can be implemented in the same library as the control or in a separate, registered library.

EventSinkChanged

procedure EventSinkChanged(const EventSink: IUnknown);

This method is called when the control’s container connects a new event sink interface to the object’s events connection point.  Override this method and store a private version of the sink if you want your object to fire events other than the standard events.  The EventSink parameter is the container’s event sink interface.  This interface can be cast to IDispatch or any dispinterface in order to fire events to the container.

This method is also called when the container wishes to change or remove the event sink interface. If EventSink is nil the event sink is being removed.

GetPropertyString

function GetPropertyString(DispID: Integer;      var S: string): Boolean;

This method is called when the Object Inspector wants to obtain a user-readable string to represent a property’s current value.  This string is displayed in the Object Inspector next to the property’s name.

Overriding this method provides the opportunity to display a property’s current value in a human-readable form rather than in its native format.  For example, a font property might be shown as “Arial, 12pt”.

ActiveX containers call this method repeatedly, once for each property in the Object Inspector.

The DispID parameter indicates the property requested.  If you want this property to display a special string, set the S parameter to the string value and return True from this method.

Returning False indicates that no special string is needed to display the value.  In this case, the Object Inspector will display the property’s value in a standard way.

GetPropertyStrings

function GetPropertyStrings(DispID: Integer; Strings: TStrings): Boolean;

This method is called when an Object Inspector wants to obtain a list of property values that the user can choose from. The Object Inspector normally calls this method when the user clicks the dropdown arrow next to a property in the Object Inspector, but before the picklist is displayed, in order to determine the values to place into the picklist.  The Object Inspector displays these values in a dropdown list next to the property’s value in the Object Inspector.

The DispID parameter indicates the property whose value strings are being requested.  If you want the Object Inspector to show a picklist for the property, override this method and call the Strings.AddObject method to add the items to the list.

In the string parameter of Strings.AddObject, provide the text that will appear in the picklist for the entry.

In the object parameter to Strings.AddObject, provide a unique “cookie value”.  This cookie value will be passed back to the GetPropertyValue method to identify which item was selected by the user. Although you can choose any unique value for the cookie value, a common convention is to use an integer value (0 for the first picklist item, 1 for the second, and so on).

Returning False indicates that no picklist should be shown for the property specified by DispID.

Returning True indicates that a picklist should be displayed, and the items in the picklist have been added to the Strings object.

This method works in combination with the GetPropertyValue method, which translates the selected string into a Variant value after the user selects an entry in the picklist.  If you implement this method for a property, you should also implement GetPropertyValue.

GetPropertyValue

procedure GetPropertyValue(DispID, Cookie: Integer;  var Value: Variant);

This method is called to convert a picklist selection into a Variant value when the user selects a property value from a picklist in the Object Inspector.  You should override this method when you have implemented the GetPropertyStrings method to define a picklist.

The DispID parameter identifies the property being modified.  The Cookie parameter identifies the item that was selected by the user.

In your implementation of this method, you should return a Variant value that corresponds to the selected item in the Value parameter.

InitializeControl

procedure InitializeControl;

This method is called after the control and the VCL control are fully created.   Overriding this method provides the opportunity to further initialize an ActiveX object after the VCL control has been created.

In particular, you should bind the OLE event-firing methods to the VCL control’s event properties in InitializeControl.

LoadFromStream

procedure LoadFromStream(const Stream: IStream);

Loads the ActiveX control’s state from the stream.  The default implementation of this method loads the stored properties of the VCL control from the stream.

You can override this method to restore any additional data that was saved by the SaveToStream method. To retrieve the extra data, call  the Stream.Read method.  The data you load must match the data that was saved in the SaveToStream method.

If you override this method, be sure to call the inherited method to restore the VCL object’s properties, unless you specifically want to prevent the control’s properties from being restored from the Stream.

PerformVerb

procedure PerformVerb(Verb: Integer);

This method is called to execute a custom verb.  Override this method when you have added verbs to the ActiveX object’s factory using the TActiveXControlFactory.AddVerb method.

The Verb paraemeter indicates which verb should be executed.  This number matches the identifier supplied in the factory’s AddVerb method.

SaveToStream

procedure SaveToStream(const Stream: IStream);

Saves the ActiveX control’s state to the provided stream.  The default implementation of this method saves all the stored properties of the VCL control to the stream.

You can override this method to save any additional data required by the control. .To save the extra data, call  the Stream.Write  method..  If you do save additional data, override LoadFromStream to make sure the data can be restored correctly.

If you override this method, be sure to call the inherited method to restore the object’s properties, unless you specifically want to prevent them from being saved.

WndProc

procedure WndProc(var Message: TMessage);

This method hooks the window procedure of the VCL implementation control .  This gives the ActiveX controller object the opportunity to handle messages of interest to ActiveX controls, such as focus change, activation, etc.

Normally there is no need to override or call this method.  The method is declared protected virtual to allow future extension of the Delphi ActiveX framework.

This method normally passes the message through to the control’s original message handler, unless the ActiveX container instructs the ActiveX object to filter out the message from the processing stream.


Implementing ActiveX Control Behaviors with TActiveXControl

Adding Properties and Methods

To add properties and methods to an implementation class, you need to design and implement a dual interface.   The TypeWriter is a convenient tool for specifying an interface’s properties and methods.  This produces a type library and a .PAS file that describe the interface. Since the interface is a dual interface, the properties are implemented using getter and setter methods in the .PAS file..  These methods are declared as safecall, meaning they automatically implement OLE conventions for errors and exception handling.

Add the interface to the declaration of the control’s implementation class.  You now need to provide implementations for each of the methods declared in the interface.

The interface implementation methods simply pass through the behavior to the VCL control.  In some cases, such as methods that take fonts as parameters, you will need to add some code to convert OLE data types to native Delphi types.

Because the automation interface methods are declared safecall, you do not have to implement OLE exception code for these methods—the Delphi compiler handles this for you.

Accessing the Container Site

The ActiveX control class keeps a reference to its site in the FOleClientSite member.  The site can implement a variety of interfaces, and a control can query any site interface from any other.  Some containers also provide the control with access to extended applicaiton services through the IServiceProvider interface.

Firing Events

An ActiveX control can fire events to its container.  To specify which events can be fired by the control, design a dispatch interface using the TypeWriter.  This produces a type library entry for the interface  and declares an Object Pascal version in the library’s interface source file.  Unlike the automation interface, do not add the events interface to your control’s interface list.  The control will not receive these events--the container does.

To fire the events to the container, you need to obtain a reference to the container’s event sink.  The container normally passes its event sink to the control when the control is being inserted into the container.  The TActiveXControl class’ implementation stores this in the FEventSink member.

Note that not all containers install an event sink, and that the event sink can change over time.  Therefore, you should always use the current value of FEventSink, and check it for nil before firing the event.  You should also check the value of the FEventsFrozen property, since the container can also ask the control to stop sending events.

You can call the event sink by casting  the sink interface to the events dispinterface, and calling the method.

You should implement the event firing code in a method whose prototype is compatible with the VCL control’s corresponding event property.  You can tie this VCL event handler to the VCL control after it is created, in the InitializeControl method.

The following is a complete implementation of code for firing an OLE Click event:

TMyControl.OnClick( Sender: TObject )

begin

  if FEventSink <> nil and not FEventsFrozen then

    IStdEvents(FEventSink).Click;

end;

Persistence

The term persistence refers an object’s ability to save itself into a file, stream, or other medium, or restore itself from that medium.  The default behavior of  TActiveXControl is to save or restore the VCL control’s properties in response to OLE persistence requests.  This is sufficient behavior for most purposes. If you want your control to save more or less information than this default, you can do so by overriding the LoadFromStream or SaveToStream methods.

Adding Property Pages

A property-page dialog is a user-friendly way to edit the properties of your ActiveX control.  Unlike the Object Inspector commonly found in programming tools, a property-page dialog is a tabbed dialog that provides an easier-to-understand interface for non-programmer users.

The TActiveXControl class contains code to implement the property-page dialog, but you must provide the dialog with the tabbed pages. You can specify the property pages in the dialog by overriding the DefinePropertyPages method.  Each property page is implemented in a Delphi form.  You can create new property pages using the ActiveX Property Page Expert.

Implementing Per-Property Browsing

The TActiveXControl class provides methods for displaying custom property types in the Object Inspector.  If your object has properties that are not normally convertable to a string (for example, a font, or a data table), you can provide some code to let the Object Inspector show a custom string. You can also provide the Object Inspector with a picklist of values for the user to choose from.

To implement per-property browsing, override the GetPropertyString, GetPropertyStrings and GetPropertyValues methods.


TPropertyPage

TPropertyPage is an abstract class that implements a Delphi Form that can be used as a property page for an ActiveX control.

TPropertyPage extends TForm by adding the functionality needed to embed the form in an ActiveX property page dialog.  It also adds methods to copy property values from the object to the form’s controls, and vice versa.

To implement your own property page for an ActiveX object, derive a new class from TPropertyPage, design the form, then override the UpdateObject and UpdatePropertyPage methods.  The Delphi IDE provides an expert for creating a skeleton property page.  See the Programmer’s Guide for more information.

TPropertyPage provides a helper method, Modified, that your form can call whenever any of the controls on the form are changed by the user.  Calling the Modified method will notify the property dialog that the user has modified the object’s properties.  Normally this will enable the Apply button on the property dialog.

The property page maintains a connection to the object it is editing, using the OleObject property.  This property is a Variant and can be used to read and write the object’s properties.

TPropertyPage Properties

OleObject: Variant

Contains a reference to the ActiveX control being edited by the page. Use this property to access the property values and methods of the control.

TPropertyPage PublicMethods

Modified

The Modified method is a helper method that you can call to notify the property page’s container that the user has edited one of the controls on the page.  The property dialog will enable the Apply button in response to this method call.

To make sure the property dialog’s Apply button is enabled whenever the user edits a control on your form, create an OnChanged handler in your form and connect it to the OnChanged event of each of the controls on the form.  In the OnChanged handler, call the Modified method.

UpdatePropertyPage

The UpdatePropertyPage method is called to copy the property values from the ActiveX Object to controls on the form.  This method is normally called when the property page is first shown, or when the user undoes changes to the property dialog.

You should override this method to copy the property values from the ActiveX object to controls on your form.

UpdatePropertyPage is the reverse of UpdateObject, which copies the property values from controls on the form to the ActiveX object.

UpdateObject

The UpdateObject method is called to copy property values from the controls on the form to the ActiveX Object.

You should override this method to copy data from the controls on the form into properties of the ActiveX Object.

UpdateObject is the reverse of UpdatePropertyPage, which copies the property values from the ActiveX Object to controls on the form.


TActiveXPropertyPageFactory

Implements a COM factory for an ActiveX property page.

A property page is a COM object, which means the module it is implemented in must provide a factory for the system to be able to create the object.  The TActiveXPropertyPageFactory class is a factory for ActiveX property pages, which you implement by subclassing the TPropertyPage class.

To add a property page factory to your program, create the factory object in the initialization section of the unit that defines the property page form.

TActiveXPropertyPageFactory Public Methods

Create

constructor Create(ComServer: TComServerObject;  PropertyPageClass: TPropertyPageClass; const ClassID: TGUID);

Creates a property page factory and binds that factory to property page class.  The ComServer parameter is a pointer to the COM server object for the ActiveX library.  This is normally the global variable, ComServer, declared in the ComServ unit.

The PropertyPageClass parameter is the class object of the property page’s implementing class.  The implementing class must be derived form TPropertyPage.  This class implements the form that is to appear in the property dialog.

The ClassID parameter is a GUID that identifies the property page’s class.  Each property page should have its own GUID.  See the Programmer’s Guide for information on how to create a GUID.

GetOleFont

procedure GetOleFont(Font: TFont; var OleFont: IFontDisp);

Creates an OLE font object that is directly mapped to a native Delphi TFont.

OLE conventions implement fonts as automation objects that contain the properties of the font.  This procedure creates an adapter object that maps the properties of a native Delphi TFont into an OLE font object, thereby allowing an OLE client to access the font properties of a VCL control. Call this function when you implement a method that retrieves a TFont value from a VCL object and returns it as an OLE font.

The Font parameter is a pointer to the native TFont. This can be the Font property of a VCL control.  The OLE font is returned in the OleFont parameter. The resulting OLE font object is a direct connection to the native Delphi font, so changing the OLE font’s properties will change the TFont’s properties, and vice-versa.

SetOleFont

procedure SetOleFont(Font: TFont; const OleFont: IFontDisp);

Connects an OLE font object to a TFont object, and copies the properties of the OLE font object to the TFont.

OLE conventions implement fonts as automation objects that contain the properties of the font.  This procedure associates an OLE font object (the OleFont parameter) with a native Delphi VCL font (the Font parameter), allowing the font to be accessed by both OLE programs and Delphi programs.  The TFont object is initialized to have the same properties as OleFont.

Call this method when you implement an automation method that takes an OLE font as a parameter, and which needs to pass the font along as a native TFont object.


TFontAdapter

A helper class used to connect a Delphi native TFont object to an OLE font object.

You would normally never create an object of this class.  Instead, you should call the GetOleFont procedure to create the binding between a TFont and an OLE font object.


TFontNotifier

A helper class used to connect an OLE font object to a Delphi native TFont object, and keep the two objects synchronized. This class receives notifications from the OLE font objects about changes to its properties, and

You would normally never create an object of this class.  Instead, you should call the GetOleFont procedure to create the binding between a TFont and an OLE font object.


dispinterface IStdEvents

A dispatch interface that declares the standard OLE events commonly implemented in an ActiveX control.

IStdEvents is a dispatch interface that declares standard OLE events.  The standard OLE events are Click, DblClick, KeyDown, KeyPress, KeyUp, MouseDown, MouseMove and MouseUp.  A control can use the dispatch interface methods to notify its container when these events happen.

Ordinarily, you would not need to call the container site directly, since the TActiveXControl class provides helper methods that fire these events for you.    The helper methods are StdClickEvent, StdDblClickEvent, StdKeyDownEvent, StdKeyPressEvent, StdKeyUpEvent, StdMouseDownEvent, StdMouseMoveEvent, StdMouseUpEvent.

Because this is a dispinterface, calling methods on this interface always executes the methods through OLE Automation.

dispinterface Public Methods

Click;

procedure Click;

Indicates that the user clicked down and up on the control.  For some controls (for example, checkboxes), this event should be fired when the value of the control is changed. Call this method when your control receives an OnClick VCL event.

DblClick;

procedure DblClick;

Indicates that the user double-clicked on the control. Call this method when your control receives an OnDblClick VCL event.

KeyDown

procedure KeyDown(var KeyCode: Smallint;  Shift: Smallint);

Indicates that the user pressed a key down when the control had focus.

The KeyCode parameter indicates the virtual-key code of the key that was pressed.

The recipient of the event may modify the key code.  In this case, the control should process the modified key instead. Changing the key code to 0 cancels the keystroke so that the control receives no character.

The Shift parameter contains a combination of the following flags:

1:	The SHIFT key was pressed during the action.

2:	The CTRL key was pressed during the action.

4:	The ALT key was pressed during the action.

KeyPress

procedure KeyPress(var KeyAscii: Smallint);

ndicates that the user pressed and released a key, and that key has been converted to an Ansi character representation.  Call this method when your control receives an OnKeyPress event.

The KeyAscii parameter contains the character value of the key that was pressed.  The recipient of the event may alter the value of KeyAscii.  In this case, the control should process the modified key instead.

The recipient of the message may change the contents of the KeyCode, for example change lower-case characters to upper case.  In this case, the control should process the modified key instead.  Changing the key code to 0 cancels the keystroke so that the control receives no character.

KeyUp

procedure KeyUp(var KeyCode: Smallint;  Shift: Smallint);

Indicates that the user released a key up when the control had focus.  The KeyCode parameter indicates the virtual-key code of the key that was released.   The recipient of the event may modify the key code.  In this case, the control should process the modified key instead. Changing the key code to 0 cancels the keystroke so that the control receives no character.

The shift parameter contains a combination of the following flags:

1:	The SHIFT key was pressed during the action.

2:	The CTRL key was pressed during the action.

4:	The ALT key was pressed during the action.

MouseDown

procedure MouseDown(Button, Shift: Smallint;      X, Y: Integer);

Indicates that the user has pressed a mouse button down on the control.

The Button parameter contains the numeric value of the mouse button pressed. It can contain one of the following values:

1:	The left mouse button was pressed down.

2:	The middle mouse button was pressed down.

4:	The right mouse button was pressed down.

The Shift parameter contains a combination of the following flags:

1:	The SHIFT key was pressed during the action.

2:	The CTRL key was pressed during the action.

4:	The ALT key was pressed during the action.

The X and Y parameters contain the coordinates of the cursor when a mouse button was pressed down. The coordinates are relative to the upper-left corner of the control window.

MouseMove

procedure MouseMove(Button, Shift: Smallint;      X, Y: Integer);

Indicates that the user has moved the mouse while the cursor is over the control..

The Button parameter indicates the numeric value of the mouse button pressed. It can contain one of the following values:

1:	The left mouse button was pressed down.

2:	The middle mouse button was pressed down.

4:	The right mouse button was pressed down.

The Shift parameter contains a combination of the following flags:

1:	The SHIFT key was pressed during the action.

2:	The CTRL key was pressed during the action.

4:	The ALT key was pressed during the action.

The X and Y parameters contain the coordinates of the cursor when a mouse button was pressed down. These coordinates are relative to the upper-left corner of the control window.


TActiveXControlFactory

The factory class for ActiveX control objects.

An ActiveX control is a COM object, which means the module it is implemented in must provide a factory object for the system to be able to create it.  The TActiveXControlFactory class is a factory for ActiveXcontrols, which are implemented using the TActiveXControl class.

For more information about COM factories, see TCOMFactory.

To add an ActiveX control factory to your program, create the factory object in the initialization section of the unit that defines the TActiveXControl object.

TActiveXControlFactory Properties

EventIID

property EventIID: TGUID;

The interface GUID for the control’s events interface.

EventTypeInfo

property EventTypeInfo: ITypeInfo;

The type information object for the object’s events interface.

MiscStatus

property MiscStatus: Integer;

Status bits that are stored in the registry

ToolboxBitmapID

property ToolboxBitmapID: Integer;

A resource identifier of the ActiveX control.

WinControlClass

property WinControlClass: TWinControlClass;

The class object of the VCL control class that implements the Windows behavior for the ActiveX control.

TActiveXControlFactory Public Methods

Create

constructor Create(ComServer: TComServerObject;ActiveXControlClass: TActiveXControlClass; WinControlClass: TWinControlClass; const ClassID: TGUID; ToolboxBitmapID: Integer);

Creates and initializes a new TActiveXControlFactory object.  The ComServer parameter is a pointer to the COM server object for the module containing the property page factory.   This is normally a global variable, ComServer, declared in the ComServ unit.

The ActiveXControlClass parameter is the class object for the controller object that implements the ActiveX control.  This object should be derived from TActiveXControl.

The WinControlClass parameter is the class object for the VCL control class that implements the Windows behavior of the control.  This object should be derived from TWinControl.

The ClassID parameter is the GUID of the ActiveX control.  Each COM object should have a unique GUID.  See the Programmer’s Guide for more on how to make a GUID.

AddVerb

procedure AddVerb(Verb: Integer; const VerbName: string);

Adds a verb to the object’s list of supported verbs as negative numbers or zero:

In the Verb parameter, pass an number to identify the verb.  User-defined verbs are normally small positive numbers. OLE also defines commonly used verbs:

OLEIVERB_PRIMARY	0

OLEIVERB_SHOW	-1

OLEIVERB_OPEN	-2

OLEIVERB_HIDE	-3

OLEIVERB_UIACTIVATE	-4

OLEIVERB_INPLACEACTIVATE	-5

OLEIVERB_DISCARDUNDOSTATE	-6

OLEIVERB_PROPERTIES	-7

In the VerbName parameter, pass the text to display when presenting the user with a choice of verbs.  In some OLE containers, the verb text is displayed in a popup menu when the user right-clicks on a control.

Wlacing an ampersand (&) before a character in this string causes the character to be displayed underlined in a popup menu.

TActiveXControlFactory Protected Virtual Methods

UpdateRegistry

procedure UpdateRegistry(Register: Boolean);

Adds or removes registry entries for the object.

This method is called to add or remove the control from the system registry.  The default behavior of this method is to add normal ActiveX control registry keys, server file information, and type library information to the registry.  It also adds the verbs added with the AddVerb method.

If the Register parameter is True, the control should add its keys to the system registry.  If it is False, the control should remove its keys from the registry.

Override this method if you want your control to store more information in the registry than the base implementation stores.  If you do override this method, be sure to call the inherited implementation in the correct order.  When adding registry entries (i.e. Register is True), call the inherited method before adding custom entries.  When removing registry entries (i.e. Register is False), call the inherited method after removing the custom entries.